├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── client ├── .DS_Store ├── assets │ ├── .DS_Store │ ├── Aeonik │ │ ├── .DS_Store │ │ ├── Aeonik-Air.otf │ │ ├── Aeonik-AirItalic.otf │ │ ├── Aeonik-Black.otf │ │ ├── Aeonik-BlackItalic.otf │ │ ├── Aeonik-Bold.otf │ │ ├── Aeonik-BoldItalic.otf │ │ ├── Aeonik-Light.otf │ │ ├── Aeonik-LightItalic.otf │ │ ├── Aeonik-Medium.otf │ │ ├── Aeonik-MediumItalic.otf │ │ ├── Aeonik-Regular.otf │ │ ├── Aeonik-RegularItalic.otf │ │ ├── Aeonik-Thin.otf │ │ └── Aeonik-ThinItalic.otf │ ├── Group 1.png │ ├── Group 2.png │ ├── Group 3.png │ ├── Group 4.png │ ├── Group 5.png │ ├── Group 6.png │ ├── Hando Soft │ │ ├── HandoSoft-Black.ttf │ │ ├── HandoSoft-BlackOblique.ttf │ │ ├── HandoSoft-Bold.ttf │ │ ├── HandoSoft-BoldOblique.ttf │ │ ├── HandoSoft-ExtraBold.ttf │ │ ├── HandoSoft-ExtraBoldOblique.ttf │ │ ├── HandoSoft-ExtraLight.ttf │ │ ├── HandoSoft-ExtraLightOblique.ttf │ │ ├── HandoSoft-HairLine.ttf │ │ ├── HandoSoft-HairLineOblique.ttf │ │ ├── HandoSoft-Light.ttf │ │ ├── HandoSoft-LightOblique.ttf │ │ ├── HandoSoft-Medium.ttf │ │ ├── HandoSoft-MediumOblique.ttf │ │ ├── HandoSoft-Oblique.ttf │ │ ├── HandoSoft-Regular.ttf │ │ ├── HandoSoft-SemiBold.ttf │ │ ├── HandoSoft-SemiBoldOblique.ttf │ │ ├── HandoSoft-Thin.ttf │ │ └── HandoSoft-ThinOblique.ttf │ ├── Logo.jpg │ ├── Logo.png │ ├── Readme Logo.png │ ├── downloading.png │ ├── loss_graphing_panel.gif │ ├── model_architecture.gif │ ├── readme logo (2).png │ ├── verticalNav-graph.png │ └── verticalNav-home.png ├── components │ ├── AnalyticsTile.jsx │ ├── App.jsx │ ├── BiasAnalytics.jsx │ ├── ExportButton.jsx │ ├── GraphIcon.jsx │ ├── Logo.jsx │ ├── LossPlot.jsx │ ├── OptimizerAnalytics.jsx │ ├── ReactFlowNeuralNetwork.jsx │ ├── VerticalTabNav.jsx │ ├── lossAnalytics.jsx │ └── weightAnalytics.jsx ├── index.html ├── index.js └── style │ ├── AnalyticsTile.scss │ ├── App.scss │ ├── barChart.scss │ └── index.scss ├── handshake.js ├── handshake.test.js ├── package-lock.json ├── package.json ├── server.js ├── server └── socketController.js └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .prettierrc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 |

9 |
10 | 11 | 12 |

13 | 14 | An all in one data visualization tool for [TensorFlow.js](https://www.tensorflow.org/js) developers 15 | 16 | ![GitHub top language](https://img.shields.io/github/languages/top/oslabs-beta/FlowSpace)   17 | ![GitHub package.json version](https://img.shields.io/github/package-json/v/oslabs-beta/flowspace)   18 | ![npm](https://img.shields.io/npm/v/flowspace.js)   19 | ![GitHub](https://img.shields.io/github/license/oslabs-beta/flowspace)   20 | ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/oslabs-beta/flowspace/dev)   21 | ![GitHub contributors](https://img.shields.io/github/contributors/oslabs-beta/flowspace)   22 | ![GitHub Repo stars](https://img.shields.io/github/stars/oslabs-beta/FlowSpace?style=social)   23 | 24 |

25 | 26 | 27 | 38 | 39 |
40 |

About FlowSpace


41 | 42 | Problem 43 | 44 | Despite the fact that machine learning plays a crucial part in advancing the tech industry, many developers view it as intimidating due to its complexity, which consequently discourages many developers from engaging with it. To address the problem, Google’s Brain Team developed TensorFlow.js in 2018 to help bring machine learning to JavaScript developers with little to no experience in machine learning. While TensorFlow.js is simplified, it lacks direct support for Tensorboard, the visualization tool for its counterpart, TensorFlow. 45 | 46 | Solution 47 | 48 |

Our team’s core mission was to develop a real time model and data visualization tool for TensorFlow.js that requires minimal set-up by the developer while simultaneously providing them with all the necessary information required to fully comprehend their machine learning model. This allows the developer to make well informed decisions in regards to how they need to alter their model to improve accuracy while lowering the required number of epochs per training cycle. 49 | This led the team to develop FlowSpace, a light-weight npm package that generates highly valuable data and intuitive visuals whilst remaining effortless to set-up. 50 | 51 | FlowSpace is developed using agile methodology in collaboration with the tech accelerator OS Labs (opensourcelabs.io)

52 | 53 | 54 | 55 | --- 56 | 57 | 58 |
59 | Table of Contents 60 |
    61 |
  1. 62 | Tech-Stack 63 |
  2. 64 |
  3. 65 | Install and Set-up 66 |
  4. 67 |
  5. 68 | Using FlowSpace Features 69 |
  6. 70 | 73 |
  7. 74 | License 75 |
  8. 76 |
  9. 77 | Contacts 78 |
  10. 79 |
80 |
81 | 82 |
83 | 84 | 115 | 116 |
117 | Install and Set-up 118 | 119 |
120 | 121 | 1. Install our npm package in your terminal. 122 | 123 | ```sh 124 | npm install flowspace.js 125 | ``` 126 | 127 | 2. Import HandShake class from the FlowSpace node_modules. 128 | 129 | ```sh 130 | import { HandShake } from "flowspace"; 131 | ``` 132 | 133 | 3. Declare your variable for the TensorFlow createModel function. 134 | 135 | ```sh 136 | const model = createModel(); 137 | ``` 138 | 139 | 4. Instantiate a new instance of the HandShake class, passing in your model object. 140 | 141 | ```sh 142 | const visualizer = new HandShake(model); 143 | ``` 144 | 145 | 5. Ensure when training the model, pass the lossCallback method from the HandShake instance into the fit method. 146 | 147 | ```sh 148 | ## Example below ## 149 | 150 | model.fit(trainingFeatureTensor, trainingLabelTensor, { 151 | epochs: 20, 152 | callbacks: { 153 | onEpochEnd: visualizer.lossCallback 154 | }, 155 | }); 156 | ``` 157 | 158 | 6. When ready, simply run the FlowSpace functionality in your terminal. 159 | 160 | ```sh 161 | npx flowspace 162 | ``` 163 | 164 |
165 | 166 |
167 | Using FlowSpace Features
168 | 169 |
170 | 171 | 172 |
173 |
174 | 175 | 177 | 178 |
179 | License 180 | 181 | MIT License 182 | 183 | Copyright (c) 2022 OSLabs Beta 184 | 185 | Permission is hereby granted, free of charge, to any person obtaining a copy 186 | of this software and associated documentation files (the "Software"), to deal 187 | in the Software without restriction, including without limitation the rights 188 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 189 | copies of the Software, and to permit persons to whom the Software is 190 | furnished to do so, subject to the following conditions: 191 | 192 | The above copyright notice and this permission notice shall be included in all 193 | copies or substantial portions of the Software. 194 | 195 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 196 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 197 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 198 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 199 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 200 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 201 | SOFTWARE. 202 | 203 |
204 | 205 |
206 | The Team 207 | 208 | - [Mark Alexander](https://github.com/MarkA772) 209 | - [Saif Beiruty](https://github.com/saifbeiruty) 210 | - [Laurence Diarra](https://github.com/ld17282) 211 | - [Mike Oakes](https://github.com/MOakes7) 212 | - [Sabre Nguyen](https://github.com/klsabren) 213 | 214 |
215 | -------------------------------------------------------------------------------- /client/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/.DS_Store -------------------------------------------------------------------------------- /client/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/.DS_Store -------------------------------------------------------------------------------- /client/assets/Aeonik/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/.DS_Store -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-Air.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Air.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-AirItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-AirItalic.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Black.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-BlackItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-BlackItalic.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Bold.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-BoldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-BoldItalic.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Light.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-LightItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-LightItalic.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Medium.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-MediumItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-MediumItalic.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Regular.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-RegularItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-RegularItalic.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-Thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Thin.otf -------------------------------------------------------------------------------- /client/assets/Aeonik/Aeonik-ThinItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-ThinItalic.otf -------------------------------------------------------------------------------- /client/assets/Group 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 1.png -------------------------------------------------------------------------------- /client/assets/Group 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 2.png -------------------------------------------------------------------------------- /client/assets/Group 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 3.png -------------------------------------------------------------------------------- /client/assets/Group 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 4.png -------------------------------------------------------------------------------- /client/assets/Group 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 5.png -------------------------------------------------------------------------------- /client/assets/Group 6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 6.png -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Black.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-BlackOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-BlackOblique.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Bold.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-BoldOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-BoldOblique.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ExtraBold.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-ExtraBoldOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ExtraBoldOblique.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ExtraLight.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-ExtraLightOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ExtraLightOblique.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-HairLine.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-HairLine.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-HairLineOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-HairLineOblique.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Light.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-LightOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-LightOblique.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Medium.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-MediumOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-MediumOblique.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-Oblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Oblique.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Regular.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-SemiBold.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-SemiBoldOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-SemiBoldOblique.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Thin.ttf -------------------------------------------------------------------------------- /client/assets/Hando Soft/HandoSoft-ThinOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ThinOblique.ttf -------------------------------------------------------------------------------- /client/assets/Logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Logo.jpg -------------------------------------------------------------------------------- /client/assets/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Logo.png -------------------------------------------------------------------------------- /client/assets/Readme Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Readme Logo.png -------------------------------------------------------------------------------- /client/assets/downloading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/downloading.png -------------------------------------------------------------------------------- /client/assets/loss_graphing_panel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/loss_graphing_panel.gif -------------------------------------------------------------------------------- /client/assets/model_architecture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/model_architecture.gif -------------------------------------------------------------------------------- /client/assets/readme logo (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/readme logo (2).png -------------------------------------------------------------------------------- /client/assets/verticalNav-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/verticalNav-graph.png -------------------------------------------------------------------------------- /client/assets/verticalNav-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/verticalNav-home.png -------------------------------------------------------------------------------- /client/components/AnalyticsTile.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../style/AnalyticsTile.scss'; 3 | import LossIcon from '../assets/Group 1.png'; 4 | import BiasIcon from '../assets/Group 2.png'; 5 | import WeightIcon from '../assets/Group 3.png'; 6 | import ActivationIcon from '../assets/Group 4.png'; 7 | import OptimizerIcon from '../assets/Group 6.png'; 8 | 9 | const AnalyticsTile = ({ info }) => { 10 | const { 11 | type, 12 | value, 13 | description, 14 | color, 15 | boldName, 16 | } = info; 17 | 18 | return ( 19 |
20 |
21 | {type === 'Loss' && ( 22 | 23 | )} 24 | {type === 'Bias' && ( 25 | 26 | )} 27 | {type === 'Max Weight' && ( 28 | 29 | )} 30 | {type === 'Optimizer' && ( 31 | 32 | )} 33 | {type === 'Activation' && ( 34 | 35 | )} 36 |
37 |
38 | {value} 39 |
40 |
41 | {type} 42 |
43 |
44 | {boldName}{description} 45 |
46 |
47 | ); 48 | }; 49 | 50 | export default AnalyticsTile; -------------------------------------------------------------------------------- /client/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../style/App.scss'; 3 | import VerticalTabNav from './VerticalTabNav.jsx'; 4 | 5 | 6 | function App() { 7 | return ( 8 |
9 |
10 | 11 |
12 |
13 | ); 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /client/components/BiasAnalytics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import AnalyticsTile from './AnalyticsTile.jsx'; 3 | 4 | const BiasAnalytics = ({ socket }) => { 5 | 6 | // hook for setting the value of biasData in state 7 | const [ biasData, setBiasData ] = useState(); 8 | 9 | useEffect(() => { 10 | // listening for sentBiasData event from socket.io server 11 | socket.on('sentBiasData', (BiasData) => { 12 | if (!BiasData) { 13 | // display when data is not available 14 | setBiasData('Ø'); 15 | return; 16 | } 17 | // setBiasData 18 | setBiasData(BiasData.toFixed(5).toString()); 19 | }); 20 | return () => { 21 | // stop listening for events 22 | socket.off('sentBiasData'); 23 | } 24 | }, []); 25 | 26 | return ( 27 | 34 | ); 35 | }; 36 | 37 | export default BiasAnalytics; 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /client/components/ExportButton.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import DownloadIcon from '../assets/downloading.png'; 3 | 4 | // component for the export button icon on the tab component 5 | const ExportButton = (props) => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default ExportButton; -------------------------------------------------------------------------------- /client/components/GraphIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import GraphIcon from '../assets/verticalNav-graph.png'; 3 | 4 | // component for the graph icon on the tab component 5 | export default function GraphIconComponent() { 6 | return ( 7 | 8 | ) 9 | } -------------------------------------------------------------------------------- /client/components/Logo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import LogoFS from '../assets/Logo.png'; 3 | 4 | // component for the FlowSpace Logo icon on the tab component 5 | export default function LogoFSComponent() { 6 | return ( 7 | 8 | ) 9 | } -------------------------------------------------------------------------------- /client/components/LossPlot.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { scaleLinear, max, min, extent } from 'd3'; 3 | import '../style/barChart.scss'; 4 | 5 | const height = '350'; 6 | const width = '950'; 7 | const margin = {top: 20, right: 20, bottom: 50, left: 80} 8 | 9 | const ScatterPlot = (props) => { 10 | const [data, setData] = useState([]) 11 | 12 | const innerHeight = height - margin.top - margin.bottom 13 | const innerWidth = width - margin.left - margin.right 14 | 15 | useEffect(() => { 16 | props.socket.emit('onClick'); 17 | props.socket.on('sentLossDataPlot', (lossData) => { 18 | setData(lossData); 19 | }); 20 | return () => { 21 | props.socket.off('sentLossDataPlot'); 22 | } 23 | }, []); 24 | 25 | const yScale = scaleLinear() 26 | .domain([0, max(data, d => d.loss)]) 27 | .range([innerHeight, 0]) 28 | 29 | const xScale = scaleLinear() 30 | .domain([min(data, d => d.epoch), max(data, d => d.epoch)]) 31 | .range([0, innerWidth]) 32 | .nice(); 33 | 34 | 35 | return ( 36 |
37 | 38 | 39 | 40 | {xScale.ticks().map(tickValue => ( 41 | 42 | 43 | {tickValue} 44 | 45 | ))} 46 | Epoch 47 | 48 | {yScale.ticks().map(tickValue => ( 49 | 50 | 51 | {tickValue} 52 | 53 | ))} 54 | Loss 55 | 56 | { data.map(d => ) } 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
70 | ) 71 | } 72 | 73 | export default ScatterPlot; 74 | -------------------------------------------------------------------------------- /client/components/OptimizerAnalytics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import AnalyticsTile from './AnalyticsTile.jsx'; 3 | 4 | const OptimizerAnalytics = ({ socket }) => { 5 | 6 | // hooks for setting the value of optimizerData and optimizerLearningRate in state 7 | const [ optimizerData, setOptimizerData ] = useState(); 8 | const [ optimizerLearningRate, setOptimizerLearningRate ] = useState(''); 9 | 10 | useEffect(() => { 11 | // listening for sentOptimizerData event 12 | socket.on('sentOptimizerData', (optimizerIterations, optimizerLearningRate) => { 13 | if (!optimizerIterations) { 14 | // display when data is not available 15 | setOptimizerData('Ø'); 16 | return; 17 | } 18 | setOptimizerData(optimizerIterations); 19 | setOptimizerLearningRate(optimizerLearningRate); 20 | }); 21 | return () => { 22 | // stop listening for events 23 | socket.off('sentOptimizerData'); 24 | } 25 | }, []); 26 | 27 | return ( 28 | 35 | ); 36 | }; 37 | 38 | export default OptimizerAnalytics; -------------------------------------------------------------------------------- /client/components/ReactFlowNeuralNetwork.jsx: -------------------------------------------------------------------------------- 1 | // import { node } from '@tensorflow/tfjs-node'; 2 | import React, { useCallback, useState, useEffect } from 'react'; 3 | import ReactFlow, { useNodesState, useEdgesState, addEdge, ReactFlowProvider} from 'react-flow-renderer'; 4 | 5 | // create Node class 6 | class Node { 7 | constructor(layerInfo, nodeInfo) { 8 | this.layerInfo = layerInfo; 9 | this.nodeInfo = nodeInfo; 10 | } 11 | } 12 | 13 | // style object for nodes 14 | const nodeStyle = { 15 | width: '5rem', 16 | height: '5rem', 17 | borderRadius: '50%', 18 | background: 'linear-gradient(to top, #DB4437, #EA8419, #F4B400)', 19 | color: '#fff', 20 | borderRadius: '50px', 21 | border: 'none', 22 | outline: 'none', 23 | cursor: 'pointer', 24 | boxShadow:'0 15px 30px rgb(179, 197, 234, .75)', 25 | } 26 | 27 | // declare arrays to hold mapped node and edge (connections) information 28 | let nodeInfo = []; 29 | let initialNodes = []; 30 | let initialEdges = []; 31 | 32 | // helper function to get the biggest layer height (most nodes) in the model 33 | const getBiggestLayerHeight = (layerInfo) => { 34 | // get the height of the input layer 35 | let height = 80 + (layerInfo[0].input_shape - 1) * 100; 36 | 37 | // loop through to find the largest height amongst the layers 38 | for (let keys in layerInfo) { 39 | height = Math.max(height, layerInfo[keys].layer_height_px); 40 | } 41 | // return the height 42 | return height; 43 | }; 44 | 45 | 46 | /** 47 | * @name parseLayer function to automatically parse any model into nodes and edges 48 | * @param {object} layerInfo 49 | * @param {hook} setNodes for setting the node information in state 50 | * @param {hook} setEdges for setting the edge information in state 51 | * @param {array} allWeights all the weight data from the model. used for rendering line thicknesses 52 | * @returns 53 | */ 54 | function parseLayer(layerInfo, setNodes, setEdges, allWeights) { 55 | // break if socket hasn't recieved layerInfo 56 | if (!layerInfo) return; 57 | 58 | // declare a varibale to store the biggest layer 59 | const maxHeight = getBiggestLayerHeight(layerInfo); 60 | 61 | // get the height of the input layer 62 | const inputHeight = 80 + (layerInfo[0].input_shape - 1) * 100; 63 | 64 | // populate input layer 65 | initialNodes = []; 66 | initialEdges = []; 67 | 68 | for (let i = 0; i < layerInfo[0].input_shape; i++) { 69 | let nodeInfo; 70 | 71 | // check if the input layer has the most nodes 72 | if (inputHeight === maxHeight) { 73 | // populate node information 74 | nodeInfo = { 75 | id: `input${i + 1}`, 76 | sourcePosition: 'right', 77 | type: 'input', 78 | position: { x: 0, y: 100 * i }, 79 | className: 'clay', 80 | style: nodeStyle, 81 | }; 82 | } else { 83 | // modifier to correctly place node along y-axis 84 | let yHeight = (maxHeight - inputHeight) / 2; 85 | 86 | // populate node information 87 | nodeInfo = { 88 | id: `input${i + 1}`, 89 | sourcePosition: 'right', 90 | type: 'input', 91 | position: { x: 0, y: 100 * i + yHeight }, 92 | className: 'clay', 93 | style: nodeStyle, 94 | }; 95 | } 96 | 97 | // create new instance of Node class with corresponing layer and node info 98 | const node = new Node(layerInfo[0], nodeInfo); 99 | 100 | // push new Node instance into intial nodes array 101 | initialNodes.push(node); 102 | } 103 | 104 | // populate hidden layers 105 | for (let keys in layerInfo) { 106 | for (let i = 0; i < layerInfo[keys].output_shape; i++) { 107 | let nodeInfo = {}; 108 | 109 | // check to see if the node is in the output layer (effects x-position) 110 | if (layerInfo[keys].layer_number === Object.keys(layerInfo).length - 1) { 111 | // populate node information 112 | if (layerInfo[keys].layer_height_px === maxHeight) { 113 | nodeInfo = { 114 | id: `layer${Number(keys) + 1}-node${i + 1}`, 115 | targetPosition: 'left', 116 | type: 'output', 117 | position: { x: (Number(keys) + 1) * 300, y: 100 * i }, 118 | style: nodeStyle, 119 | }; 120 | } else { 121 | // modifier to correctly place node along y-axis 122 | let yHeight = (maxHeight - layerInfo[keys].layer_height_px) / 2; 123 | // populate node info 124 | nodeInfo = { 125 | id: `layer${Number(keys) + 1}-node${i + 1}`, 126 | targetPosition: 'left', 127 | type: 'output', 128 | position: { x: (Number(keys) + 1) * 300, y: 100 * i + yHeight }, 129 | style: nodeStyle, 130 | }; 131 | } 132 | } else { 133 | // populate node information for hidden layers 134 | if (layerInfo[keys].layer_height_px === maxHeight) { 135 | nodeInfo = { 136 | id: `layer${Number(keys) + 1}-node${i + 1}`, 137 | sourcePosition: 'right', 138 | targetPosition: 'left', 139 | position: { x: (Number(keys) + 1) * 300, y: 100 * i }, 140 | style: nodeStyle, 141 | }; 142 | } else { 143 | let yHeight = (maxHeight - layerInfo[keys].layer_height_px) / 2; 144 | nodeInfo = { 145 | id: `layer${Number(keys) + 1}-node${i + 1}`, 146 | sourcePosition: 'right', 147 | targetPosition: 'left', 148 | position: { x: (Number(keys) + 1) * 300, y: 100 * i + yHeight }, 149 | style:nodeStyle, 150 | }; 151 | } 152 | } 153 | 154 | // create new instance of Node class with corresponing layer and node info 155 | const node = new Node(layerInfo[keys], nodeInfo); 156 | 157 | // push new Node instance into intial nodes array 158 | initialNodes.push(node); 159 | } 160 | } 161 | 162 | // returs the shape of the next layer to the right 163 | const getNextLayerShape = (initialNodes, layerNumber) => { 164 | let nextLayerShape = 0; 165 | // loop through initial nodes until the first node of the next layer is identified 166 | for (let i = 0; i < initialNodes.length; i++) { 167 | if (initialNodes[i].layerInfo.layer_number === layerNumber + 1) { 168 | nextLayerShape = initialNodes[i].layerInfo.output_shape; 169 | break; 170 | } 171 | } 172 | return nextLayerShape; 173 | }; 174 | 175 | // loop through nodes 176 | for (let nodeNum = 0; nodeNum < initialNodes.length; nodeNum++) { 177 | let currentLayerShape = 0; 178 | let nextLayerNumber = 0; 179 | let nextLayerShape = 0; 180 | 181 | // check if the current node is smaller than the first layer's input shape 182 | // i.e if current node is within the first layer 183 | if (nodeNum < layerInfo[0].input_shape) { 184 | currentLayerShape = Number(initialNodes[nodeNum].layerInfo.output_shape); 185 | nextLayerShape = currentLayerShape; 186 | nextLayerNumber = initialNodes[nodeNum].layerInfo.layer_number + 1; 187 | } else { 188 | // if the current node is in the hidden layers 189 | currentLayerShape = Number(initialNodes[nodeNum].layerInfo.output_shape); 190 | nextLayerShape = getNextLayerShape(initialNodes, initialNodes[nodeNum].layerInfo.layer_number); 191 | nextLayerNumber = initialNodes[nodeNum].layerInfo.layer_number + 2; 192 | } 193 | // used to track aand normalze the edge thickness based off Weight 194 | let counter = 0; 195 | 196 | // loop through entire layer and set edges 197 | for (let i = 0; i < nextLayerShape; i++) { 198 | // populate attributes of edge object 199 | const edge = { 200 | id: `${initialNodes[nodeNum].nodeInfo.id}-layer${nextLayerNumber}-node${i + 1}`, 201 | source: initialNodes[nodeNum].nodeInfo.id, 202 | type: 'simplebezier', 203 | target: `layer${nextLayerNumber}-node${i + 1}`, 204 | style: { 205 | stroke: '#F4B400', 206 | strokeWidth: allWeights ? allWeights[counter] : 1.5 // default thickness until weight data comes in 207 | }, 208 | }; 209 | 210 | counter++; 211 | 212 | initialEdges.push(edge); 213 | } 214 | } 215 | 216 | // set the nodeInfo arrayand setEdges array 217 | nodeInfo = initialNodes.map((x) => x.nodeInfo); 218 | setNodes(nodeInfo); 219 | setEdges(initialEdges); 220 | } 221 | 222 | 223 | // NeuralNetwork functional component 224 | const NeuralNetwork = (props) => { 225 | /* 226 | ReactFlow helper hooks to create new local state for nodes and edges 227 | Exposes a onNodesChange/onEdgesChange functions to be passed as props to the React Flow component 228 | */ 229 | const [nodes, setNodes, onNodesChange] = useNodesState([]); 230 | const [edges, setEdges, onEdgesChange] = useEdgesState([]); 231 | 232 | // onConnect handler function to add edges 233 | const onConnect = useCallback( 234 | (params) => setEdges((els) => addEdge(params, els)),[] 235 | ); 236 | 237 | // onLoad handler function to fit the ReactFlow canvas to the 238 | // neural network 239 | const onLoad = (reactFlowInstance) => { 240 | reactFlowInstance.fitView(); 241 | } 242 | 243 | // lifecycle method for syncing component to external system (i.e. socket.io server) 244 | useEffect(() => { 245 | // listening for incomingData socket event from the socket-io server (see line 88 server.js) 246 | props.socket.on('incomingData', (data, allWeights) => { 247 | // parse data accordingly 248 | parseLayer(data, setNodes, setEdges, allWeights); 249 | }); 250 | return () => { 251 | // no longer listening for socket events 252 | props.socket.off('incomingData'); 253 | } 254 | }, []); 255 | 256 | return ( 257 | 258 | 270 | 271 | ); 272 | }; 273 | 274 | export default NeuralNetwork; 275 | -------------------------------------------------------------------------------- /client/components/VerticalTabNav.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Tabs from '@mui/material/Tabs'; 4 | import Tab from '@mui/material/Tab'; 5 | import Box from '@mui/material/Box'; 6 | import { createTheme } from '@mui/material/styles'; 7 | import Logo from './Logo.jsx'; 8 | import GraphIcon from './GraphIcon.jsx'; 9 | import NeuralNetwork from './ReactFlowNeuralNetwork.jsx'; 10 | import socketIO from 'socket.io-client'; 11 | import LossPlot from './LossPlot.jsx'; 12 | import LossAnalytics from './LossAnalytics.jsx'; 13 | import WeightAnalytics from './WeightAnalytics.jsx'; 14 | import ExportButton from './ExportButton.jsx'; 15 | import BiasAnalytics from './BiasAnalytics.jsx'; 16 | import OptimizerAnalytics from './OptimizerAnalytics.jsx'; 17 | 18 | // connection to socket.io server on FlowSpace's backend 19 | const socket = socketIO.connect('http://localhost:3333'); 20 | 21 | // override MUI default theme to match FlowSpace UI 22 | const theme = createTheme({ 23 | palette: { 24 | primary: { 25 | light: '#757ce8', 26 | main: '#3f50b5', 27 | dark: '#002884', 28 | contrastText: '#fff', 29 | }, 30 | secondary: { 31 | light: '#ff7961', 32 | main: '#f44336', 33 | dark: '#ba000d', 34 | contrastText: '#000', 35 | }, 36 | }, 37 | }); 38 | 39 | // event handler to emit socket information on click 40 | function clickEvent() { 41 | socket.emit("onClick"); 42 | } 43 | 44 | // fuctional component to place the content of the respective tab 45 | function TabPanel(props) { 46 | /* Default props 47 | * @props children (node): component similar to the table row. 48 | * @props value (Object): The current panel, for toggling hidden/not hidden 49 | * @props index (number): The number of the panel order from 0+ 50 | */ 51 | const { children, value, index, ...other } = props; 52 | 53 | return ( 54 | 68 | ); 69 | } 70 | 71 | // set the typing and requirements for the props passed to TabPanel components 72 | TabPanel.propTypes = { 73 | children: PropTypes.node, 74 | index: PropTypes.number.isRequired, 75 | value: PropTypes.number.isRequired, 76 | }; 77 | 78 | // function to toggle the component's id attribute based on the index of the tab panel 79 | function a11yProps(index) { 80 | return { 81 | id: `vertical-tab-${index}`, 82 | 'aria-controls': `vertical-tabpanel-${index}`, 83 | }; 84 | } 85 | 86 | // functional vertical tab component 87 | export default function VerticalTabs() { 88 | // hook to set the value variable 89 | const [value, setValue] = useState(0); 90 | 91 | // event handler to update value 92 | const handleChange = (event, newValue) => { 93 | setValue(newValue); 94 | }; 95 | 96 | // lifecycle method for syncing component to external system (i.e. socket.io server) 97 | useEffect(() => clickEvent(), []); 98 | 99 | return ( 100 | 110 | 111 | } style={{ textAlign:'center'}} {...a11yProps(0)} onClick={clickEvent}/> 112 | } style={{ textAlign:'center' }} {...a11yProps(1)} onClick={clickEvent}/> 113 | 114 | 115 | 116 |
117 |

Welcome,

118 |

Model Architecture

119 |
120 |
121 |
122 | 123 |
124 |
125 |
126 |

At A Glance

127 |
128 |
129 | 130 | 131 | 132 | 133 |
134 |
135 | 136 |
137 | 138 |
139 |

At A Glance

140 |
141 |
142 | 143 | 144 | 145 | 146 |
147 |
148 |
149 |
150 | ); 151 | } 152 | -------------------------------------------------------------------------------- /client/components/lossAnalytics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import AnalyticsTile from './AnalyticsTile.jsx'; 3 | 4 | const LossAnalytics = ({ socket }) => { 5 | 6 | // hooks for setting the value of lossData and lossMethod in state 7 | const [ lossData, setLossData ] = useState(); 8 | const [ lossMethod, setLossMethod ] = useState(''); 9 | 10 | 11 | useEffect(() => { 12 | // listening for sentLossDataAnalytics event from socket.io server 13 | socket.on('sentLossDataAnalytics', (lossData, lossMethod) => { 14 | if (!lossData.length) { 15 | // display when data is not available 16 | setLossData('Ø'); 17 | return; 18 | } 19 | // set data accordingly 20 | setLossData(lossData[lossData.length - 1].loss.toFixed(6).toString()); 21 | setLossMethod(lossMethod); 22 | }); 23 | return () => { 24 | // stop listening for events 25 | socket.off('sentLossDataAnalytics'); 26 | } 27 | }, []); 28 | 29 | return ( 30 | 38 | ); 39 | }; 40 | 41 | export default LossAnalytics; -------------------------------------------------------------------------------- /client/components/weightAnalytics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import AnalyticsTile from './AnalyticsTile.jsx'; 3 | 4 | const WeightAnalytics = ({ socket }) => { 5 | 6 | // hook for setting the value of weightData in state 7 | const [ weightData, setWeightData ] = useState(); 8 | 9 | useEffect(() => { 10 | // listening for sentWeightData event from socket.io server 11 | socket.on('sentWeightData', (WeightData) => { 12 | if (!WeightData) { 13 | // display when data is not available 14 | setWeightData('Ø'); 15 | return; 16 | } 17 | // setWeightData 18 | setWeightData(WeightData.toFixed(5).toString()); 19 | }); 20 | return () => { 21 | // stop listening for events 22 | socket.off('sentWeightData'); 23 | } 24 | }, []); 25 | 26 | return ( 27 | 34 | ); 35 | }; 36 | 37 | export default WeightAnalytics; -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FlowSpace 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './style/index.scss'; 4 | import App from './components/App.jsx'; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')); 7 | root.render( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /client/style/AnalyticsTile.scss: -------------------------------------------------------------------------------- 1 | /* create custom font face for titles/larger text*/ 2 | @font-face { 3 | font-family: 'Aeonik'; 4 | src: local('Aeonik-Bold'), url('../assets/Aeonik/Aeonik-Bold.otf') format('otf'); 5 | src: local('Aeonik-Regular'), url('../assets/Aeonik/Aeonik-Regular.otf') format('otf'); 6 | } 7 | 8 | /* create custom font face for subtitles*/ 9 | @font-face { 10 | font-family: 'HandoSoft'; 11 | src: local('HandoSoft-Regular'), url('../assets/Hando\ Soft/HandoSoft-Regular.ttf') format('ttf'); 12 | src: local('HandoSoft-Medium'), url('../assets/Hando\ Soft/HandoSoft-Medium.ttf') format('ttf'); 13 | src: local('HandoSoft-Black'), url('../assets/Hando\ Soft/HandoSoft-Black.ttf') format('ttf'); 14 | } 15 | 16 | /* make each tile a column flexbox */ 17 | .Analytics-Tile { 18 | display: flex; 19 | flex-direction: column; 20 | /* center align along main axis, vertically*/ 21 | justify-content: center; 22 | /* center align along crosss axis, horizontally */ 23 | align-items: center; 24 | margin-right: 2rem; 25 | 26 | padding: 1rem; 27 | background-color: #fff; 28 | border-radius: 30px; 29 | /* claymorphism styling */ 30 | backdrop-filter: blur(5px); 31 | background-color: rgba(255, 255, 255, 1); 32 | box-shadow: 0 10px 20px rgb(179, 197, 234, .75), 33 | inset 0px 0px 0px 0px rgba(145, 192, 255, 0.75), 34 | inset -4px -4px 8px 0px rgba(145, 193, 255, 0.30), 35 | inset 0px 10px 20px 0px rgb(255, 255, 255); 36 | } 37 | 38 | /* uniform styling and sizing of icons */ 39 | img { 40 | width: 50px; 41 | height: auto; 42 | margin-bottom: 1rem; 43 | } 44 | 45 | /* text styling for the displayed live data */ 46 | .tile-value { 47 | font-size: 50px; 48 | margin-top: .5rem; 49 | font-family: HandoSoft-Black; 50 | } 51 | 52 | /* text styling for the title of the tile*/ 53 | .tile-title { 54 | font-size: 17px; 55 | font-weight: bold; 56 | text-transform: uppercase; 57 | margin-top: 5px; 58 | color: black; 59 | } 60 | 61 | /* text styling for the description */ 62 | .tile-description { 63 | font-family: Aeonik-Regular; 64 | text-align: center; 65 | width: 235px; 66 | font-size: 15px; 67 | margin-top: 5px; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /client/style/App.scss: -------------------------------------------------------------------------------- 1 | /* create custom font face for titles/larger text*/ 2 | @font-face { 3 | font-family: 'HandoSoft'; 4 | src: local('HandoSoft-Regular'), url('../assets/Hando\ Soft/HandoSoft-Regular.ttf') format('ttf'); 5 | src: local('HandoSoft-Medium'), url('../assets/Hando\ Soft/HandoSoft-Medium.ttf') format('ttf'); 6 | } 7 | 8 | /* create custom font face for subtitles*/ 9 | @font-face { 10 | font-family: 'Aeonik'; 11 | src: local('Aeonik-Bold'), url('../assets/Aeonik/Aeonik-Bold.otf') format('otf'); 12 | src: local('Aeonik-Regular'), url('../assets/Aeonik/Aeonik-Regular.otf') format('otf'); 13 | } 14 | 15 | /* styling for overall App.jsx component*/ 16 | .App { 17 | /* default width and height */ 18 | width: 100vw; 19 | height: 100vh; 20 | 21 | /* set min width/height for window resizing*/ 22 | min-width: 1000px; 23 | min-height: 800px; 24 | 25 | /* set lightblue gradient as background*/ 26 | background-image: linear-gradient( 27 | 135deg, 28 | hsl(0deg 0% 100%) 0%, 29 | hsl(223deg 57% 98%) 13%, 30 | hsl(223deg 57% 96%) 27%, 31 | hsl(222deg 57% 94%) 40%, 32 | hsl(222deg 57% 92%) 52%, 33 | hsl(222deg 57% 90%) 64%, 34 | hsl(221deg 57% 87%) 75%, 35 | hsl(221deg 57% 85%) 85%, 36 | hsl(221deg 57% 83%) 93%, 37 | hsl(220deg 57% 81%) 100% 38 | ); 39 | /* flexbox --> direction agnostic --> increase responsivess to page resizing*/ 40 | display: flex; 41 | /* main axis as vertical axis*/ 42 | flex-direction: column; 43 | /* center along main axis, vertically*/ 44 | justify-content: center; 45 | /* center along cross-axis, horizontally*/ 46 | align-items: center; 47 | } 48 | 49 | 50 | .Dashboard { 51 | /* 90% of parent div, App*/ 52 | width: 90%; 53 | height: 90%; 54 | 55 | min-width: 950px; 56 | /* round corners to fit claymorphism styling */ 57 | border-radius: 30px; 58 | /* background of dashboard is white*/ 59 | background-color: #FAFBFF; 60 | /* shadow to make dashhboard appear as if hovering*/ 61 | box-shadow: 0 10px 20px rgb(179, 197, 234, .75); 62 | } 63 | 64 | .react-flow-rectangle { 65 | width: 99.5%; 66 | height: 100%; 67 | border-radius: 30px; 68 | backdrop-filter: blur(5px); 69 | background-color: rgba(255, 255, 255, 1); 70 | /* claymorphism styling */ 71 | box-shadow: 0 10px 20px rgb(179, 197, 234, .75), 72 | inset 0px 0px 0px 0px rgba(145, 192, 255, 0.75), 73 | inset -4px -4px 8px 0px rgba(145, 193, 255, 0.30), 74 | inset 0px 10px 20px 0px rgb(255, 255, 255); 75 | } 76 | 77 | /* styling for "Welcome" */ 78 | .analytics-header p { 79 | color: rgb(164, 162, 162); 80 | font-family: HandoSoft-Medium; 81 | font-size: 15px; 82 | margin-left: 0rem; 83 | margin-bottom: 0px; 84 | margin-top: 0px; 85 | } 86 | 87 | /* Styling for "Model Architecture" */ 88 | .analytics-header h2 { 89 | margin-top: 0px; 90 | color: black; 91 | font-family: Aeonik-Bold; 92 | font-size: 20px; 93 | margin-left: 0rem; 94 | } 95 | 96 | /* graph panel wrapper to instantiate a flexbox with columns*/ 97 | .graphPanelWrapper { 98 | display: flex; 99 | flex-direction: column; 100 | } 101 | 102 | /* styling for "At A Glance" */ 103 | .analytics-overview-header h2 { 104 | color: black; 105 | font-family: Aeonik-Bold; 106 | font-size: 20px; 107 | margin-left: 0rem; 108 | } 109 | 110 | /* custom margin spacing for overview header on main tab*/ 111 | #front-overview-header { 112 | margin-top: .3rem; 113 | margin-bottom: .3rem; 114 | } 115 | 116 | /* custom margin spacing for overview header on graph tab*/ 117 | #graph-overview-header { 118 | margin-top: 1rem; 119 | } 120 | 121 | /* due to graph tab being a flexbox, overwrite h2 display attribute */ 122 | #graph-overview-header h2 { 123 | display: inline; 124 | } 125 | 126 | /* horizontal scrolling div with analytics tiles */ 127 | .analytics-tiles { 128 | height:42%; 129 | width: 102.4%; 130 | display: flex; 131 | flex-direction: row; 132 | flex-wrap: nowrap; 133 | overflow-x: auto; 134 | box-sizing: border-box; 135 | padding-left: 1rem; 136 | 137 | .tile { 138 | flex: 0 0 auto; 139 | } 140 | } 141 | 142 | /* 143 | graph tiles need more padding due to size difference betwen ReactFlow 144 | component and D3 graph component 145 | */ 146 | #graph-tiles { 147 | padding-block: 25px; 148 | } 149 | 150 | /* remove horizontal scroll bars from analytics tile div*/ 151 | .analytics-tiles::-webkit-scrollbar { 152 | display: none; 153 | } 154 | 155 | /* hide the reactflow watermark/attribution*/ 156 | .react-flow__attribution { 157 | visibility: hidden; 158 | } 159 | 160 | /* top and bottom margin on icons set to 0, left and right margins on auto*/ 161 | .tabIcon { 162 | margin: 0 auto; 163 | } 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /client/style/barChart.scss: -------------------------------------------------------------------------------- 1 | /* create custom font face for titles/larger text*/ 2 | @font-face { 3 | font-family: 'Aeonik'; 4 | src: local('Aeonik-Bold'), url('../assets/Aeonik/Aeonik-Bold.otf') format('otf'); 5 | src: local('Aeonik-Regular'), url('../assets/Aeonik/Aeonik-Regular.otf') format('otf'); 6 | } 7 | 8 | /* create custom font face for subtitles*/ 9 | @font-face { 10 | font-family: 'HandoSoft'; 11 | src: local('HandoSoft-Regular'), url('../assets/Hando\ Soft/HandoSoft-Regular.ttf') format('ttf'); 12 | src: local('HandoSoft-Medium'), url('../assets/Hando\ Soft/HandoSoft-Medium.ttf') format('ttf'); 13 | } 14 | 15 | /* add stroke to lines */ 16 | .tick line { 17 | stroke: #C0C0BB 18 | } 19 | 20 | .tick text { 21 | fill: #635F5D; 22 | } 23 | 24 | .mark { 25 | fill: url(#MarkGradient); 26 | -webkit-filter: drop-shadow( 3px 3px 2px rgb(179, 197, 234, .75)); 27 | filter: drop-shadow( 3px 3px 2px rgb(179, 197, 234, .75)); 28 | } 29 | 30 | .xAxisLabel { 31 | font-size: 1rem; 32 | fill: #635F5D; 33 | } 34 | 35 | .yAxisLabel { 36 | transform: rotate(-90deg); 37 | font-size: 1rem; 38 | font-family: HandoSoft-Medium; 39 | fill: #635F5D; 40 | } 41 | 42 | .d3Graph { 43 | padding: 1rem; 44 | background-color: #fff; 45 | border-radius: 30px; 46 | backdrop-filter: blur(5px); 47 | background-color: rgba(255, 255, 255, 1); 48 | box-shadow: 0 10px 20px rgb(179, 197, 234, .75), 49 | inset 0px 0px 0px 0px rgba(145, 192, 255, 0.75), 50 | inset -4px -4px 8px 0px rgba(145, 193, 255, 0.30), 51 | inset 0px 10px 20px 0px rgb(255, 255, 255); 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /client/style/index.scss: -------------------------------------------------------------------------------- 1 | /* fallbacks if borwser does not support custom fonts*/ 2 | body { 3 | margin: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | /* setting fallback fonts if browser doesn't support custom fonts */ 12 | code { 13 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 14 | monospace; 15 | } 16 | -------------------------------------------------------------------------------- /handshake.js: -------------------------------------------------------------------------------- 1 | // connection to DevClient side 2 | 3 | //imports socketIO client with connection 4 | import socketIO from 'socket.io-client'; 5 | 6 | const socket = socketIO.connect('http://localhost:3333'); 7 | 8 | export class HandShake { 9 | constructor(model) { 10 | this.model = model; 11 | this.socket = socket; 12 | this.socket.emit('modelData', this.model); 13 | this.lossCallback = this.lossCallback.bind(this); 14 | } 15 | 16 | lossCallback(epoch, log) { 17 | let x = this.model.layers 18 | const allWeights = []; 19 | const allAbsMax = []; 20 | const allBiasAbsMax = []; 21 | 22 | // Let's get the max of each layer 23 | for (let i = 0; i < x.length; i++) { 24 | // This gets the weights in each layer and returns an array of weights 25 | let arr = x[i].getWeights()[0].dataSync() 26 | // allWeights is an array that contains the weights of all the layers 27 | // allWeights is used to help in rendering the connection thickness 28 | allWeights.push(...Array.from(arr)) 29 | // We then find the max and min values in the array 30 | let max = Math.max(...arr) 31 | let min = Math.min(...arr) 32 | // The maximum absolute value is then stored in allAbsMax 33 | if(Math.abs(max) < Math.abs(min)) { 34 | allAbsMax.push(min) 35 | } else { 36 | allAbsMax.push(max) 37 | } 38 | } 39 | 40 | for (let i = 0; i < x.length; i++) { 41 | // This gets the weights in each layer and returns an array of weights 42 | let biasArr = x[i].getWeights()[1].dataSync() 43 | // We then find the max and min values in the array 44 | let max = Math.max(...biasArr) 45 | let min = Math.min(...biasArr) 46 | // The maximum absolute value is then stored in allAbsMax 47 | if(Math.abs(max) < Math.abs(min)) { 48 | allBiasAbsMax.push(min) 49 | } else { 50 | allBiasAbsMax.push(max) 51 | } 52 | } 53 | 54 | const biasResult = allBiasAbsMax.reduce( 55 | (prev, current) => Math.abs(current) > Math.abs(prev) ? current : prev 56 | , 0); 57 | 58 | // Result will be the max of node weights of all the layers 59 | const result = allAbsMax.reduce( 60 | (prev, current) => Math.abs(current) > Math.abs(prev) ? current : prev 61 | , 0); 62 | 63 | // find the absolute minimum to normalize 64 | let minimum = Infinity; 65 | for(let weight = 0; weight < allWeights.length; weight++) { 66 | minimum = Math.min(minimum, Math.abs(allWeights[weight])) 67 | } 68 | const normalizedData = [] 69 | for(let weight = 0; weight < allWeights.length; weight++) { 70 | // normalized the data between 0.8 and 10 71 | let normalizedWeight = 0.8 + ( ((Math.abs(allWeights[weight]) - minimum) * (10 - 0.8)) / Math.abs(result) - minimum) 72 | normalizedData.push(normalizedWeight) 73 | } 74 | socket.emit('modelInfo', this.model.loss, this.model.optimizer, biasResult, result, { epoch, loss: log.loss } ) 75 | socket.emit('modelData', this.model, normalizedData) 76 | 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /handshake.test.js: -------------------------------------------------------------------------------- 1 | import { HandShake } from "./handshake"; 2 | import express from "express"; 3 | const app = express(); 4 | import http from "http"; 5 | const server = http.createServer(app); 6 | import { Server } from "socket.io"; 7 | import * as tf from "@tensorflow/tfjs"; 8 | 9 | 10 | describe("testing functionality of socket.io in handshake class", () => { 11 | let handshakeInstance; 12 | let io; 13 | let model; 14 | let serverSocket; 15 | 16 | beforeAll(() => { 17 | io = new Server(server); 18 | io.on('connection', (socket) => { 19 | serverSocket = socket; 20 | }); 21 | model = tf.sequential(); 22 | model.add( 23 | tf.layers.dense({ 24 | units: 1, 25 | inputShape: [1], 26 | activation: "linear", 27 | useBias: true, 28 | }) 29 | ); 30 | model.add(tf.layers.dense({ units: 10, activation: "linear", useBias: true })); 31 | model.add(tf.layers.dense({ units: 4, activation: "linear", useBias: true })); 32 | model.add(tf.layers.dense({ units: 1, activation: "linear", useBias: true })); 33 | const optimizer = tf.train.sgd(0.1); 34 | model.compile({ optimizer, loss: "meanSquaredError" }); 35 | }); 36 | 37 | afterAll(() => { 38 | io.close(); 39 | handshakeInstance.socket.close(); 40 | }); 41 | 42 | 43 | test("checks initial data", (done) => { 44 | io.on("connection", () => { 45 | serverSocket.on("modelData", (modelData, allWeights) => { 46 | modelData = JSON.parse(modelData); 47 | expect(typeof modelData).toBe('object'); 48 | expect(modelData.class_name).toBe('Sequential'); 49 | done(); 50 | }); 51 | }); 52 | handshakeInstance = new HandShake(model); 53 | }); 54 | 55 | test("checks model type from lossCallback", (done) => { 56 | serverSocket.on("modelData", (modelData, allWeights) => { 57 | modelData = JSON.parse(modelData); 58 | expect(modelData.class_name).toBe('Sequential'); 59 | done(); 60 | }); 61 | handshakeInstance.lossCallback(1, {loss: 4}); 62 | }); 63 | 64 | test("checks number of weight data points from lossCallback", (done) => { 65 | serverSocket.on("modelData", (modelData, allWeights) => { 66 | expect(allWeights.length).toBe(55); 67 | done(); 68 | }); 69 | handshakeInstance.lossCallback(1, {loss: 4}); 70 | }); 71 | 72 | test("checks normalization of weight data from lossCallback is between 0.8 and 10", (done) => { 73 | serverSocket.on("modelData", (modelData, allWeights) => { 74 | expect(Math.round(Math.max(...allWeights))).toBe(10); 75 | expect(Math.round(Math.min(...allWeights) * 10)).toBe(8); 76 | done(); 77 | }); 78 | handshakeInstance.lossCallback(1, {loss: 4}); 79 | }); 80 | 81 | test("checks correct data types from lossCallback", (done) => { 82 | serverSocket.on('modelInfo', (loss, optimizer, biasResult, result, epochLossData) => { 83 | expect(loss).toBe('meanSquaredError'); 84 | expect(typeof biasResult).toBe('number'); 85 | expect(typeof result).toBe('number'); 86 | done(); 87 | }); 88 | handshakeInstance.lossCallback(4, {loss: 7}); 89 | }); 90 | 91 | test("checks model learning rate from lossCallback", (done) => { 92 | serverSocket.on('modelInfo', (loss, optimizer, biasResult, result, epochLossData) => { 93 | expect(optimizer.learningRate).toBe(0.1); 94 | done(); 95 | }); 96 | handshakeInstance.lossCallback(4, {loss: 7}); 97 | }); 98 | 99 | test("checks epoch loss data from lossCallback", (done) => { 100 | serverSocket.on('modelInfo', (loss, optimizer, biasResult, result, epochLossData) => { 101 | expect(epochLossData.epoch).toBe(4); 102 | expect(epochLossData.loss).toBe(7); 103 | done(); 104 | }); 105 | handshakeInstance.lossCallback(4, {loss: 7}); 106 | }); 107 | 108 | server.listen(3333); 109 | }) 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flowspace.js", 3 | "version": "0.0.1", 4 | "description": "a data visualization tool for TensorFlow.js", 5 | "main": "handshake.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "NODE_OPTIONS='--experimental-vm-modules' jest", 9 | "start": "nodemon ./server.js", 10 | "dev": "concurrently \"webpack serve --hot --progress --color\" \"nodemon ./server.js\"", 11 | "format": "prettier --write \"***/**/*.{js,jsx}\"", 12 | "build": "webpack" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/oslabs-beta/FlowSpace.git" 17 | }, 18 | "author": "Mark Alexander, Saif Beiruty, Laurence Diarra, Mike Oakes, and Sabre Nguyen", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/oslabs-beta/FlowSpace/issues" 22 | }, 23 | "homepage": "https://github.com/oslabs-beta/FlowSpace#readme", 24 | "dependencies": { 25 | "@babel/preset-env": "^7.18.10", 26 | "@babel/preset-react": "^7.18.6", 27 | "@emotion/react": "^11.10.4", 28 | "@emotion/styled": "^11.10.4", 29 | "@mui/material": "^5.10.3", 30 | "@tensorflow/tfjs-node": "^3.19.0", 31 | "concurrently": "^7.3.0", 32 | "d3": "^7.6.1", 33 | "express": "^4.18.1", 34 | "nodemon": "^2.0.20", 35 | "prettier": "^2.7.1", 36 | "react": "^18.2.0", 37 | "react-dom": "^18.2.0", 38 | "react-flow-renderer": "^10.3.16", 39 | "react-router-dom": "^6.3.0", 40 | "react-scripts": "5.0.1", 41 | "sass-loader": "^13.0.2", 42 | "socket.io": "^4.5.1", 43 | "socket.io-client": "^4.5.1", 44 | "url-loader": "^4.1.1", 45 | "web-vitals": "^2.1.4" 46 | }, 47 | "bin": { 48 | "flowspace": "./server.js" 49 | }, 50 | "devDependencies": { 51 | "jest": "^29.2.2", 52 | "svg-inline-loader": "^0.8.2", 53 | "webpack": "^5.74.0", 54 | "webpack-cli": "^4.10.0" 55 | }, 56 | "jest": { 57 | "testEnvironment": "jest-environment-node", 58 | "transform": {} 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import express from 'express'; 4 | const app = express(); 5 | import http from 'http'; 6 | const server = http.createServer(app); 7 | import { Server } from 'socket.io'; 8 | import path from 'path'; 9 | import { fileURLToPath } from 'url'; 10 | 11 | const __filename = fileURLToPath(import.meta.url); 12 | 13 | // socket-io server 14 | const io = new Server(server, { 15 | cors: { 16 | origin: ["http://localhost:8080", "http://localhost:8081"], 17 | }, 18 | }); 19 | 20 | app.get("/", (req, res) => { 21 | res.sendFile(path.dirname(__filename) + "/build/index.html"); 22 | }); 23 | 24 | app.get("/bundle.js", (req, res) => { 25 | res.sendFile(path.dirname(__filename) + "/build/bundle.js"); 26 | }); 27 | 28 | app.get("/export", (req, res) => { 29 | res.set({ 30 | 'Content-Type': 'application/json-attachment', 31 | 'content-disposition': 'attachment; filename="export.json"' 32 | }); 33 | res.send(JSON.stringify(lossData)); 34 | }); 35 | 36 | // model must be doubly parsed prior to using this function 37 | const parseModel = (model) => { 38 | model = JSON.parse(model); 39 | 40 | let layer = {}; 41 | let input_shape; 42 | let output_shape; 43 | let params; 44 | 45 | for (let i = 0; i < model.config.layers.length; i++) { 46 | // check if it's the first layer or not to set input shape 47 | model.config.layers[i].config.batch_input_shape 48 | ? (input_shape = model.config.layers[i].config.batch_input_shape[1]) 49 | : (input_shape = model.config.layers[i - 1].config.units); 50 | 51 | // set output shape 52 | output_shape = model.config.layers[i].config.units; 53 | 54 | // calculate layer height 55 | const layer_height_px = 80 + (output_shape - 1) * 100; 56 | 57 | // calculate connections 58 | params = input_shape * output_shape + output_shape; 59 | 60 | layer[i] = { 61 | layer_number: i, 62 | layer_type: model.config.layers[i].config.name.includes("dense") 63 | ? "DENSE" 64 | : "NOT DENSE", 65 | input_shape, 66 | output_shape, 67 | layer_height_px, 68 | params, 69 | }; 70 | } 71 | return layer; 72 | }; 73 | 74 | // session data: every connection between flowspace (socket.io server) and 75 | // dev's environment (socket.io client) temporarily store data in these variables 76 | // to be sent to Flowspaces's fronted (socket.io client) 77 | let lossData = []; 78 | let weightData; 79 | let lossMethodHolder; 80 | let savedModel; 81 | const allWeightData = []; 82 | let biasData; 83 | let optimizerIterations; 84 | let optimizerLearingRate; 85 | 86 | // connect to socket-io client on dev's end 87 | io.on("connection", (socket) => { 88 | console.log("client connected"); 89 | 90 | // modelData event, means our socket.io-server is listening for this event from dev's 91 | // socket.io client via the Handshake 92 | socket.on("modelData", (data, allWeights) => { 93 | const d = parseModel(data); 94 | savedModel = parseModel(data); 95 | allWeightData.push(allWeights); 96 | io.sockets.emit("incomingData", d, allWeights); 97 | }); 98 | 99 | // modelData event, means our socket.io-server is listening for this event from dev's 100 | // socket.io client via the Handshake 101 | socket.on("modelInfo", (lossMethod, optimizer, maxBias, maxWeight, loss) => { 102 | if (loss.epoch === 0) { 103 | lossData = []; 104 | } 105 | lossData.push(loss); 106 | io.sockets.emit("sentOptimizerData", optimizer.iterations_, optimizer.learningRate); 107 | io.sockets.emit("sentLossDataPlot", lossData); 108 | io.sockets.emit("sentLossDataAnalytics", lossData, lossMethod); 109 | io.sockets.emit("sentWeightData", maxWeight); 110 | io.sockets.emit('sentBiasData', maxBias) 111 | weightData = maxWeight; 112 | biasData = maxBias; 113 | optimizerIterations = optimizer.iterations_; 114 | optimizerLearingRate = optimizer.learningRate; 115 | lossMethodHolder = lossMethod 116 | }); 117 | 118 | // onClick vent, means our socket.io-server is listening for this event from dev's 119 | // socket.io client via the Handshake 120 | socket.on("onClick", () => { 121 | io.sockets.emit("incomingData", savedModel, allWeightData[allWeightData.length - 1]); 122 | io.sockets.emit("sentLossDataPlot", lossData); 123 | io.sockets.emit("sentLossDataAnalytics", lossData, lossMethodHolder); 124 | io.sockets.emit("sentWeightData", weightData); 125 | io.sockets.emit("sentBiasData", biasData); 126 | io.sockets.emit("sentOptimizerData", optimizerIterations, optimizerLearingRate); 127 | }); 128 | 129 | // diconnect vent, means our socket.io-server is listening for this event from dev's 130 | // socket.io client via the Handshake 131 | socket.on("disconnect", () => { 132 | console.log("client disconnected"); 133 | }); 134 | }); 135 | 136 | server.listen(3333, () => { 137 | console.log("Server listening on 3333"); 138 | }); 139 | 140 | 141 | -------------------------------------------------------------------------------- /server/socketController.js: -------------------------------------------------------------------------------- 1 | const socketController = {}; 2 | 3 | function parseLayer(layerInfo) { 4 | // input layer 5 | for (let i = 0; i < layerInfo[0].input_shape; i++) { 6 | // populate node information 7 | const nodeInfo = { 8 | id: `input${i + 1}`, 9 | sourcePosition: 'right', 10 | type: 'input', 11 | data: { label: `Input-${i + 1}` }, 12 | position: { x: 0, y: 100 * i }, 13 | className: 'clay', 14 | style: { 15 | width: '5rem', 16 | height: '5rem', 17 | borderRadius: '50%', 18 | fontWeight: 'bold', 19 | border: 'none', 20 | padding: '2rem .5rem', 21 | fontFamily: 'inherit', 22 | backgrounBlendMode: 'multiply', 23 | color: 'rgb(235, 234, 234)', 24 | background: 'linear-gradient(225deg, #181818, #2e2e2e)', 25 | boxShadow: '5px 5px 10px #191919, 5px -5px 10px #292929', 26 | }, 27 | }; 28 | 29 | // create new instance of Node class with corresponing layer and node info 30 | const node = new Node(layerInfo[0], nodeInfo); 31 | 32 | // push new Node instance into intial nodes array 33 | initialNodes.push(node); 34 | } 35 | 36 | // hidden layers 37 | for (let keys in layerInfo) { 38 | for (let i = 0; i < layerInfo[keys].output_shape; i++) { 39 | let nodeInfo = {}; 40 | 41 | // check to see if the node is in the output layer 42 | if (layerInfo[keys].layer_number === Object.keys(layerInfo).length - 1) { 43 | // populate node information 44 | nodeInfo = { 45 | id: `layer${Number(keys) + 1}-node${i + 1}`, 46 | targetPosition: 'left', 47 | type: 'output', 48 | data: { label: `Output-${i + 1}` }, //`Layer${Number(keys)+1}-Node-${i+1}` 49 | position: { x: (Number(keys) + 1) * 300, y: 100 * i }, 50 | style: { 51 | width: '5rem', 52 | height: '5rem', 53 | borderRadius: '50%', 54 | fontWeight: 'bold', 55 | border: 'none', 56 | padding: '2rem .5rem', 57 | fontFamily: 'inherit', 58 | backgrounBlendMode: 'multiply', 59 | color: 'rgb(235, 234, 234)', 60 | background: 'linear-gradient(225deg, #181818, #2e2e2e)', 61 | boxShadow: '5px 5px 10px #191919, 5px -5px 10px #292929', 62 | }, 63 | }; 64 | } else { 65 | // populate node information 66 | nodeInfo = { 67 | id: `layer${Number(keys) + 1}-node${i + 1}`, 68 | sourcePosition: 'right', 69 | targetPosition: 'left', 70 | data: { label: `Layer${Number(keys) + 1}-Node-${i + 1}` }, 71 | position: { x: (Number(keys) + 1) * 300, y: 100 * i }, 72 | style: { 73 | width: '5rem', 74 | height: '5rem', 75 | borderRadius: '50%', 76 | fontWeight: 'bold', 77 | border: 'none', 78 | padding: '2rem .5rem', 79 | fontFamily: 'inherit', 80 | backgrounBlendMode: 'multiply', 81 | color: 'rgb(235, 234, 234)', 82 | background: 'linear-gradient(225deg, #181818, #2e2e2e)', 83 | boxShadow: '5px 5px 10px #191919, 5px -5px 10px #292929', 84 | }, 85 | }; 86 | } 87 | 88 | // create new instance of Node class with corresponing layer and node info 89 | const node = new Node(layerInfo[keys], nodeInfo); 90 | 91 | // push new Node instance into intial nodes array 92 | initialNodes.push(node); 93 | } 94 | } 95 | 96 | const getNextLayerShape = (initialNodes, layerNumber) => { 97 | let nextLayerShape = 0; 98 | for (let i = 0; i < initialNodes.length; i++) { 99 | if (initialNodes[i].layerInfo.layer_number === layerNumber + 1) { 100 | nextLayerShape = initialNodes[i].layerInfo.output_shape; 101 | break; 102 | } 103 | } 104 | return nextLayerShape; 105 | }; 106 | 107 | for (let nodeNum = 0; nodeNum < initialNodes.length; nodeNum++) { 108 | let currentLayerShape = 0; 109 | let nextLayerNumber = 0; 110 | let nextLayerShape = 0; 111 | 112 | if (nodeNum < layerInfo[0].input_shape) { 113 | currentLayerShape = Number(initialNodes[nodeNum].layerInfo.output_shape); 114 | nextLayerShape = currentLayerShape; 115 | nextLayerNumber = initialNodes[nodeNum].layerInfo.layer_number + 1; 116 | } else { 117 | currentLayerShape = Number(initialNodes[nodeNum].layerInfo.output_shape); 118 | nextLayerShape = getNextLayerShape( 119 | initialNodes, 120 | initialNodes[nodeNum].layerInfo.layer_number 121 | ); 122 | nextLayerNumber = initialNodes[nodeNum].layerInfo.layer_number + 2; 123 | } 124 | 125 | for (let i = 0; i < nextLayerShape; i++) { 126 | const edge = { 127 | id: `${initialNodes[nodeNum].nodeInfo.id}-layer${nextLayerNumber}-node${ 128 | i + 1 129 | }`, 130 | source: initialNodes[nodeNum].nodeInfo.id, 131 | type: 'simplebezier', 132 | target: `layer${nextLayerNumber}-node${i + 1}`, 133 | }; 134 | 135 | initialEdges.push(edge); 136 | } 137 | } 138 | console.log('these are our nodes', initialNodes); 139 | console.log('these are our edges', initialEdges); 140 | } 141 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 2 | import path from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | 7 | export default { 8 | entry: path.resolve(path.dirname(__filename), 'client', 'index.js'), 9 | output: { 10 | path: path.resolve(path.dirname(__filename), 'build'), 11 | filename: 'bundle.js' 12 | }, 13 | mode: "development", 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.js$|jsx/, 18 | exclude: /node_modules/, 19 | use: { 20 | loader: 'babel-loader', 21 | options: { 22 | presets: ['@babel/preset-env', '@babel/preset-react'] 23 | } 24 | } 25 | }, 26 | { 27 | test: /\.s?css$/, 28 | exclude:/(node_modules|bower_components)/, 29 | use: [ 30 | { loader: 'style-loader' }, 31 | { loader: 'css-loader' } 32 | ] 33 | }, 34 | { 35 | test: /\.(jpe?g|png|gif|woff|woff2|eot|ttf|svg)(\?[a-z0-9=.]+)?$/, 36 | use: { 37 | loader: 'url-loader?limit=100000' 38 | } 39 | }, 40 | // { 41 | // test: /\.scss/, 42 | // exclude: /node_modules/, 43 | // use: [ 44 | // { loader: 'style-loader' }, 45 | // { loader: 'sass-loader' } 46 | // ] 47 | // } 48 | ] 49 | }, 50 | plugins: [ 51 | new HtmlWebpackPlugin({ 52 | template: "./client/index.html" 53 | }), 54 | ], 55 | devtool: 'eval-cheap-source-map', 56 | devServer: { 57 | proxy: { 58 | '/export': 'http://localhost:3333' 59 | } 60 | } 61 | }; --------------------------------------------------------------------------------