├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── axes-size.gif ├── choose-chart.gif ├── compilation-stats.json ├── electron-scripts └── preload.js ├── export.gif ├── logo.png ├── main.js ├── npm_package_entry.js ├── package.json ├── penguin-icon-old.png ├── penguin-icon.png ├── select-properties.gif ├── src ├── AllRoutes.jsx ├── AppRoutes.jsx ├── Archives │ ├── charts │ │ ├── BarChart │ │ │ ├── BarChart.jsx │ │ │ ├── BarChartCodePreview.jsx │ │ │ └── BarChartForm.jsx │ │ ├── Histogram │ │ │ ├── Histogram.jsx │ │ │ ├── HistogramCodePreview.jsx │ │ │ └── HistogramForm.jsx │ │ ├── LineChartCodePreview.jsx │ │ ├── LineChartForm.jsx │ │ └── Scatterplot │ │ │ ├── ScatterPlotCodePreview.jsx │ │ │ └── ScatterPlotForm.jsx │ ├── reducers │ │ ├── barPaddingSlice.js │ │ ├── dataSlice.js │ │ ├── heightSlice.js │ │ ├── nameSlice.js │ │ ├── radiusSlice.js │ │ ├── thresholdsSlice.js │ │ ├── widthSlice.js │ │ ├── xAxisLabelSlice.js │ │ ├── xKeySlice.js │ │ ├── yAxisLabelSlice.js │ │ └── yKeySlice.js │ └── utilities │ │ ├── Archive │ │ ├── Axis.jsx │ │ ├── AxisHorizontal.js │ │ ├── AxisVertical.js │ │ ├── Axis_comments.jsx │ │ ├── Bars.jsx │ │ ├── Chart.jsx │ │ ├── Chart_comments.jsx │ │ ├── CodePreview.js │ │ ├── accessorPropsType.jsx │ │ ├── combineChartDimensions.jsx │ │ ├── createApplication.js │ │ ├── createFiles.js │ │ ├── dimensionsPropsType.jsx │ │ ├── exportButton.js │ │ ├── useAccessor.jsx │ │ ├── useChartDimensions_comments.jsx │ │ ├── useUniqueId.js │ │ └── utils.js │ │ └── exportProject.js ├── Dropdown │ ├── Dropdown.jsx │ ├── DropdownFunctionality.jsx │ ├── Menu.Item.jsx │ ├── Menu.jsx │ └── MenuButton.jsx ├── app │ ├── dictionary.js │ ├── preloadedState.js │ └── store.js ├── babel-plugin-macros.config.js ├── babel.config.js ├── components │ ├── ChartComponents │ │ ├── Chart.css │ │ ├── JSX │ │ │ ├── Arc.jsx │ │ │ ├── Axis.jsx │ │ │ ├── Bars.jsx │ │ │ ├── Chart.css │ │ │ ├── Chart.jsx │ │ │ ├── ChartWithDimensions.jsx │ │ │ ├── Circles.jsx │ │ │ ├── CodeRender.jsx │ │ │ ├── Container.jsx │ │ │ ├── ErrorBoundary.jsx │ │ │ ├── ExportCodeButton.jsx │ │ │ ├── ExportDataButton.jsx │ │ │ ├── Form.jsx │ │ │ ├── Gradient.jsx │ │ │ ├── Line.jsx │ │ │ ├── Pie.jsx │ │ │ └── Rectangle.jsx │ │ └── chartstyles.css │ ├── Charts │ │ ├── BarChart │ │ │ └── JSX │ │ │ │ └── BarChart.jsx │ │ ├── Histogram │ │ │ └── JSX │ │ │ │ └── Histogram.jsx │ │ ├── LineChart │ │ │ └── JSX │ │ │ │ └── LineChart.jsx │ │ ├── PieChart │ │ │ └── JSX │ │ │ │ └── PieChart.jsx │ │ └── ScatterPlot │ │ │ └── JSX │ │ │ └── ScatterPlot.jsx │ ├── HelloWorldcopy.js │ ├── NavBar.tsx │ └── pages │ │ ├── ChartCards.jsx │ │ ├── Homepage.jsx │ │ ├── TheCarousel.jsx │ │ ├── chart1.svg │ │ ├── chart2.svg │ │ ├── chart3.svg │ │ ├── chart4.svg │ │ └── chart5.svg ├── dashboard.png ├── features │ └── chart │ │ ├── chartsSlice.js │ │ └── propsSlice.js ├── index.d.ts ├── index.html ├── index.jsx ├── mockup.html ├── styles.css ├── topy.png └── utils │ ├── CodePreview.js │ ├── EnteredData.jsx │ ├── ExportData.js │ ├── dummyTimelineData.js │ ├── dummyfruitsdata.js │ ├── dummypenguinsdata.js │ ├── handlers.js │ ├── hooks.js │ ├── jsonFruits │ ├── jsonLine │ ├── jsonpenguins │ ├── observable.js │ ├── parseData.js │ ├── utils.js │ └── utils.ts ├── stats.json ├── tailwind.config.js ├── tsconfig.json ├── webpack-stats.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["syntax-dynamic-import"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src/* 3 | build/* 4 | 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "prettier"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 10 | "prettier", 11 | "airbnb", 12 | "airbnb-typescript", 13 | "airbnb/hooks" 14 | ], 15 | "rules": { 16 | "prettier/prettier": 2, 17 | "import/no-extraneous-dependencies": 0, 18 | "max-len": 0, 19 | "comma-dangle": 0, 20 | "curly": 0, 21 | "no-extraneous-dependencies": 0 22 | } 23 | // "parserOptions": { "project": "./tsconfig.json" } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | ignore.tsx 4 | *.tsx 5 | *.ts 6 | LibrarySample.jsx 7 | archives 8 | .tsx 9 | src/components/Charts/LineGraph/**/*.tsx.jsx 10 | src/components/Charts/Timeline/**/*.tsx.jsx 11 | installer 12 | dist 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 80 6 | } -------------------------------------------------------------------------------- /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 | # ad3lie 2 | 3 | ad3lie is an open-source application and package for creating elegant and responsive data visualizations built with React and D3. 4 | 5 | The focus of this application is to generate user customized charts that can be used in any React project. 6 | 7 | # Installation 8 | 9 | ## Download ad3lie Application 10 | 11 | Begin by downloading the app from our [website](https://ad3lie.dev/). 12 | 13 | After opening the app, choose which graph to create. Don't worry, if you decide to chose another graph, simply click on the home button and choose another graph from the home page. All of your input data will be saved for the duration of the application's life span. 14 | 15 | ![](choose-chart.gif) 16 | 17 | Input the required fields (i.e. Data, xKey, and yKey) and adjust the graph based on the inputs given 18 | 19 | ![](select-properties.gif) 20 | 21 | ![](axes-size.gif) 22 | 23 | Click the export button and select the file directory of your project for both the data file and React component. 24 | 25 | ![](export.gif) 26 | 27 | ## Install ad3lie Package 28 | 29 | Downloading ad3lie will include other necessary dependencies in order to generate your data visualizations. This allows for the use of the React component exported from the ad3lie application. 30 | 31 | Simply download the npm package 32 | 33 | ``` 34 | npm i ad3lie 35 | ``` 36 | 37 | OR 38 | 39 | ``` 40 | yarn add ad3lie 41 | ``` 42 | 43 | From here, import the React component as a child component. 44 | 45 | ``` 46 | import Chart from "./file/path/to/component" 47 | ``` 48 | 49 | From here, simply use the componet as you would any other child component in your React app. 50 | 51 | ### Happy visualizing! <|^o^|> 52 | 53 | # Documentation 54 | 55 | For more detailed information, please check the related package [documentation](https://docs.ad3lie.dev/) or go directly to our [npm package](https://www.npmjs.com/package/ad3lie). 56 | 57 | # How to Setup React App 58 | Make sure to install these npm packages 59 | ``` 60 | npm i d3 61 | npm i postcss 62 | npm i postcss-loader 63 | npm i autoprefixer 64 | ``` 65 | 66 | tailwind.config.js 67 | ``` 68 | module.exports = { 69 | content: ["./client/src/**/*.{html,js}"], 70 | theme: { 71 | extend: {}, 72 | }, 73 | plugins: [], 74 | } 75 | ``` 76 | 77 | webpack.config.js 78 | ```javascript 79 | const path = require('path'); 80 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 81 | 82 | module.exports = (env) => { 83 | return { 84 | mode: env.mode, 85 | entry: './client/src/index.js', 86 | output: { 87 | path: path.resolve(__dirname, 'client', 'build'), 88 | filename: 'bundle.js' 89 | }, 90 | module: { 91 | rules: [{ 92 | test: /\.jsx?/, 93 | use: { 94 | loader: 'babel-loader', 95 | options: { 96 | presets: ['@babel/preset-env', 97 | '@babel/preset-react'], 98 | targets: {chrome: "100"} 99 | 100 | } 101 | } 102 | }, 103 | { 104 | test: /.+\.css$/i, 105 | // exclude: /node_modules/, 106 | use: [ 107 | 'style-loader', 108 | 'css-loader', 109 | { 110 | loader: 'postcss-loader', 111 | options: { 112 | postcssOptions: { 113 | plugins: { 114 | tailwindcss: {}, 115 | autoprefixer: {}, 116 | } 117 | } 118 | } 119 | } 120 | ] 121 | } 122 | ] 123 | }, 124 | resolve: { 125 | extensions: ['', '.js', '.jsx'], 126 | alias: { 127 | 'react': path.resolve(__dirname, 'node_modules/react'), 128 | } 129 | }, 130 | 131 | plugins: [new HtmlWebpackPlugin({ 132 | template: './client/src/index.html' 133 | })], 134 | devServer: { 135 | static: './client/build', 136 | port: 8888 137 | } 138 | } 139 | } 140 | ``` 141 | 142 | Checkout our [website](https://ad3lie.dev/) to see incoming features, how to get involved, and meet our team! 143 | -------------------------------------------------------------------------------- /axes-size.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/axes-size.gif -------------------------------------------------------------------------------- /choose-chart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/choose-chart.gif -------------------------------------------------------------------------------- /electron-scripts/preload.js: -------------------------------------------------------------------------------- 1 | // Preload (Isolated World) 2 | const { contextBridge, ipcRenderer, dialog } = require('electron'); 3 | const { writeFileSync, mkdirSync } = require('fs'); 4 | const path = require('path'); 5 | console.log('1st dialog', dialog); 6 | 7 | contextBridge.exposeInMainWorld('electron', { 8 | writeFileSync, 9 | mkdirSync, 10 | path, 11 | __dirname, 12 | // dialog 13 | //difference between .send and .invoke? 14 | showSaveDialog: () => ipcRenderer.invoke('show-save-dialog') 15 | }); 16 | 17 | /* 18 | DESCRIPTION: This file appears to limit the node methods the Electron app can access. 19 | 20 | Per the docs: 21 | -Main World is the JavaScript context in which the renderer code runs (that is, the page) 22 | -Isolated World is where preload scripts run 23 | -contextBridge is a module that safely exposes APIs from the isolated context in which preload scripts run 24 | to the context in which the website or application runs (i.e., from Isolated World to Main World) 25 | 26 | We likely should not change this file unless we determine additional methods are necessary 27 | or some methods are not used. 28 | 29 | */ 30 | 31 | // Expose protected methods that allow the renderer process to use select node methods 32 | // without exposing all node functionality. This is a critical security feature 33 | // 'mainWorld" is the context that the main renderer runs in 34 | // with contextIsolation on (see webpreferences on main.js), this preload script runs isolated 35 | // "electron" is the key that injects the api into the window 36 | // to access these keys in the renderer process, we'll do window.electron 37 | // the electron object (second arg) can contain functions, strings, bools, numbers, arrays, obects in value 38 | // data primitives sent on the bridge are immutable and changes in one context won't carry over to another context 39 | -------------------------------------------------------------------------------- /export.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/export.gif -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/logo.png -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const { app, contextBridge, BrowserWindow, ipcMain, dialog } = require('electron'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const createWindow = () => { 6 | const win = new BrowserWindow({ 7 | width: 1400, 8 | height: 1200, 9 | webPreferences: { 10 | preload: path.resolve(__dirname, 'electron-scripts', 'preload.js'), 11 | devTools: true 12 | } 13 | }); 14 | 15 | // Uses Webpack Dev Server in Development 16 | // Must open 17 | // win.loadURL('http://localhost:8080/index.html'); 18 | win.loadFile('./dist/index.html'); 19 | // const contents = win.webContents 20 | // console.log(contents) 21 | }; 22 | 23 | app.whenReady().then(() => { 24 | // Setup an Event listener to listen on the 'export-chart' channel. 25 | // ipcMain.on('export-chart', (event, code, data) => { 26 | // // Make a Direcotry 27 | // // Write the Data file in directory 28 | // // Write the Code as a File in the Directory 29 | // fs.mkdirSync(path.resolve(__dirname, 'temp')); 30 | // fs.writeFileSync(path.resolve(__dirname, 'temp', 'data.json'), data) 31 | // fs.writeFileSync(path.resolve(__dirname, 'temp', 'code.js'), code) 32 | // }) 33 | // EXPORT FUNCTIONALITY 34 | // ipcMain.handle & ipcRenderer.invoke allows for a two-way communication between renderer and main 35 | // BarChartCodePreview > ipcMain.handle > > .then( {canceled, filePaths}) 36 | ipcMain.handle('show-save-dialog', (event) => { 37 | const saveDialogPromise = dialog.showOpenDialog({ 38 | properties: ['openDirectory'], 39 | buttonLabel: 'Export' 40 | }) 41 | .then(({ canceled, filePaths }) => { 42 | if (canceled) { 43 | console.log('Request Canceled') 44 | return 45 | } else { 46 | console.log('File Selected:', filePaths) 47 | return filePaths[0] 48 | } 49 | }) 50 | .catch(err => console.log('ERROR on "show-save-dialog" event: ', err)) 51 | 52 | // return promise 53 | return saveDialogPromise 54 | }); 55 | 56 | createWindow(); 57 | }); 58 | 59 | -------------------------------------------------------------------------------- /npm_package_entry.js: -------------------------------------------------------------------------------- 1 | import BarChart from './src/components/Charts/BarChart/JSX/BarChart' 2 | import Histogram from './src/components/Charts/Histogram/JSX/Histogram' 3 | import LineChart from './src/components/Charts/LineChart/JSX/LineChart' 4 | import PieChart from './src/components/Charts/PieChart/JSX/PieChart' 5 | import ScatterPlot from './src/components/Charts/ScatterPlot/JSX/ScatterPlot' 6 | 7 | export {BarChart, Histogram, LineChart, PieChart, ScatterPlot} 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ad3lie", 3 | "version": "0.0.5", 4 | "description": "", 5 | "main": "main.js", 6 | "exports": { 7 | ".": "./npm_package_entry.js" 8 | }, 9 | "scripts": { 10 | "start": "webpack && electron .", 11 | "build": "webpack", 12 | "start-dev-server": "webpack serve", 13 | "mac:installer": "electron-builder --mac", 14 | "win:installer": "electron-builder --win", 15 | "linux:installer": "electron-builder --linux", 16 | "tsc": "tsc", 17 | "lint": "eslint . --ext .ts", 18 | "lint-and-fix": "eslint . --ext .ts --fix", 19 | "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/oslabs-beta/ad3lie.git" 24 | }, 25 | "build": { 26 | "appId": "com.electron.d3act", 27 | "mac": { 28 | "category": "public.app-category.developer-tools", 29 | "icon": "penguin-icon.png", 30 | "target": [ 31 | "dmg" 32 | ] 33 | }, 34 | "dmg": { 35 | "title": "ad3lie", 36 | "backgroundColor": "#ffffff", 37 | "window": { 38 | "width": "400", 39 | "height": "300" 40 | }, 41 | "contents": [ 42 | { 43 | "x": 100, 44 | "y": 100 45 | }, 46 | { 47 | "x": 300, 48 | "y": 100, 49 | "type": "link", 50 | "path": "/Applications" 51 | } 52 | ] 53 | }, 54 | "win": { 55 | "target": [ 56 | "nsis" 57 | ], 58 | "icon": "penguin-icon.png" 59 | }, 60 | "nsis": { 61 | "oneClick": false, 62 | "uninstallDisplayName": "ad3lie-uninstaller", 63 | "allowToChangeInstallationDirectory": true 64 | }, 65 | "linux": { 66 | "target": [ 67 | "Appimage" 68 | ] 69 | }, 70 | "directories": { 71 | "output": "installer" 72 | } 73 | }, 74 | "keywords": [], 75 | "author": "", 76 | "license": "ISC", 77 | "bugs": { 78 | "url": "https://github.com/oslabs-beta/ad3lie/issues" 79 | }, 80 | "homepage": "https://github.com/oslabs-beta/ad3lie#readme", 81 | "devDependencies": { 82 | "@babel/cli": "^7.17.10", 83 | "@babel/core": "^7.18.5", 84 | "@babel/preset-env": "^7.18.0", 85 | "@babel/preset-react": "^7.17.12", 86 | "@babel/preset-typescript": "^7.17.12", 87 | "@types/d3": "7.4.0", 88 | "@types/lodash": "^4.14.182", 89 | "@types/react": "^18.0.9", 90 | "@types/react-dom": "^18.0.5", 91 | "@typescript-eslint/eslint-plugin": "^5.26.0", 92 | "@typescript-eslint/parser": "^5.26.0", 93 | "autoprefixer": "^10.4.7", 94 | "babel-loader": "^8.2.5", 95 | "css-loader": "^6.7.1", 96 | "d3": "^7.4.4", 97 | "dedent-js": "^1.0.1", 98 | "electron": "^18.2.4", 99 | "electron-builder": "^23.0.3", 100 | "eslint": "^8.16.0", 101 | "eslint-config-airbnb-typescript": "^17.0.0", 102 | "eslint-config-prettier": "^8.5.0", 103 | "eslint-plugin-prettier": "^4.0.0", 104 | "eslint-plugin-react": "^7.27.1", 105 | "eslint-plugin-react-hooks": "^4.3.0", 106 | "express": "^4.17.3", 107 | "file-loader": "^6.2.0", 108 | "html-react-parser": "^1.4.14", 109 | "html-webpack-plugin": "^5.5.0", 110 | "lodash": "^4.17.21", 111 | "node-polyfill-webpack-plugin": "^1.1.4", 112 | "path-browserify": "^1.0.1", 113 | "postcss": "^8.4.14", 114 | "postcss-loader": "^7.0.0", 115 | "prettier": "^2.6.2", 116 | "prettier-format": "^3.1.0", 117 | "prettier-standalone": "^1.3.1-0", 118 | "react": "^18.1.0", 119 | "react-dom": "^18.1.0", 120 | "react-responsive-carousel": "^3.2.23", 121 | "react-router-dom": "^6.3.0", 122 | "react-test-renderer": "^18.1.0", 123 | "resize-observer-polyfill": "^1.5.1", 124 | "rollup": "^2.61.1", 125 | "rollup-plugin-cleanup": "^3.2.1", 126 | "rollup-plugin-prettier": "^2.2.0", 127 | "rollup-plugin-size": "^0.1.0", 128 | "rollup-plugin-strip-banner": "^2.0.0", 129 | "rollup-plugin-terser": "^7.0.2", 130 | "rollup-plugin-visualizer": "^5.5.2", 131 | "style-loader": "^3.3.1", 132 | "svg-inline-loader": "^0.8.2", 133 | "tailwindcss": "^3.0.24", 134 | "ts-loader": "^9.3.0", 135 | "typescript": "^4.6.4", 136 | "undefined": "^0.1.0", 137 | "webpack": "^5.72.1", 138 | "webpack-cli": "^4.10.0", 139 | "webpack-dev-server": "^4.9.0" 140 | }, 141 | "dependencies": { 142 | "@reduxjs/toolkit": "^1.8.2", 143 | "asar": "^3.1.0", 144 | "babel-plugin-macros": "^3.1.0", 145 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 146 | "babel-prettier-parser": "^0.10.8", 147 | "eslint-config-airbnb": "^19.0.4", 148 | "eslint-plugin-import": "^2.26.0", 149 | "eslint-plugin-jsx-a11y": "^6.5.1", 150 | "express": "^4.17.3", 151 | "file-loader": "^6.2.0", 152 | "lodash": "^4.17.21", 153 | "node-polyfill-webpack-plugin": "^1.1.4", 154 | "nodeify": "^1.0.1", 155 | "prettier-format": "^3.1.0", 156 | "prop-types": "^15.8.1", 157 | "react-redux": "^8.0.2", 158 | "react-router-dom": "^6.3.0", 159 | "resize-observer-polyfill": "^1.5.1", 160 | "socket.io-client": "^4.5.1", 161 | "styled-components": "^5.3.5", 162 | "ts-node": "^10.4.0", 163 | "tw-elements": "^1.0.0-alpha12" 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /penguin-icon-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/penguin-icon-old.png -------------------------------------------------------------------------------- /penguin-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/penguin-icon.png -------------------------------------------------------------------------------- /select-properties.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/select-properties.gif -------------------------------------------------------------------------------- /src/AllRoutes.jsx: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense } from "react"; 2 | import { Route, Routes } from "react-router-dom"; 3 | import Homepage from "./components/pages/Homepage.jsx"; 4 | import dictionary from './app/dictionary.js' 5 | 6 | export const RouteHook = () => { 7 | 8 | const Container = lazy(() => import(`./components/ChartComponents/JSX/Container.jsx`)) 9 | 10 | // Programmatically creating unique routes for all charts in our dictionary 11 | const routes = Object.values(dictionary).reduce((acc, { type, name, children, properties }) => { 12 | acc.push(} />); 13 | return acc; 14 | }, []); 15 | 16 | return ( 17 | Loading...}> 18 | 19 | } /> 20 | {routes} 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default RouteHook; -------------------------------------------------------------------------------- /src/AppRoutes.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import { HashRouter } from "react-router-dom"; 3 | import AllRoutes from "./AllRoutes.jsx"; 4 | 5 | export default () => ( 6 | 7 | 8 |
9 | 10 |
11 |
12 |
13 | ); 14 | 15 | -------------------------------------------------------------------------------- /src/Archives/charts/BarChart/BarChart.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | import * as d3 from "d3" 4 | 5 | 6 | // import Chart from "./Chart/Chart" 7 | import Chart from '../utilities/Chart' 8 | import Bars from '../utilities/Bars' 9 | import Axis from "../utilities/Axis" 10 | import Gradient from "../../components/ChartComponents/JSX/Gradient"; 11 | import { useChartDimensions } from "../utilities/utils" 12 | import { accessorPropsType } from "../utilities/utils" 13 | import { useUniqueId } from "../utilities/utils" 14 | 15 | const gradientColors = ["#9980FA", "rgb(226, 222, 243)"] 16 | 17 | const BarChart = ({ data, xAccessor, label }) => { 18 | const gradientId = useUniqueId("Histogram-gradient") 19 | const [ref, dimensions] = useChartDimensions({ 20 | marginBottom: 77, 21 | }) 22 | 23 | const numberOfThresholds = 9 24 | 25 | const xScale = d3.scaleLinear() 26 | .domain(d3.extent(data, xAccessor)) 27 | .range([0, dimensions.boundedWidth]) 28 | // .nice(numberOfThresholds) 29 | 30 | const binsGenerator = d3.histogram() 31 | .domain(xScale.domain()) 32 | .value(xAccessor) 33 | .thresholds(xScale.ticks(numberOfThresholds)) 34 | 35 | const bins = binsGenerator(data) 36 | 37 | const yAccessor = d => d.length 38 | const yScale = d3.scaleLinear() 39 | .domain([0, d3.max(bins, yAccessor)]) 40 | .range([dimensions.boundedHeight, 0]) 41 | .nice() 42 | 43 | const barPadding = 2 44 | 45 | const xAccessorScaled = d => xScale(d.x0) + barPadding 46 | const yAccessorScaled = d => yScale(yAccessor(d)) 47 | const widthAccessorScaled = d => xScale(d.x1) - xScale(d.x0) - barPadding 48 | const heightAccessorScaled = d => dimensions.boundedHeight - yScale(yAccessor(d)) 49 | const keyAccessor = (d, i) => i 50 | 51 | return ( 52 |
53 | 54 | 55 | 61 | 62 | 68 | 74 | 83 | 84 |
85 | ) 86 | } 87 | 88 | BarChart.propTypes = { 89 | xAccessor: accessorPropsType, 90 | yAccessor: accessorPropsType, 91 | xLabel: PropTypes.string, 92 | yLabel: PropTypes.string, 93 | } 94 | 95 | BarChart.defaultProps = { 96 | xAccessor: d => d.x, 97 | yAccessor: d => d.y, 98 | } 99 | export default BarChart -------------------------------------------------------------------------------- /src/Archives/charts/BarChart/BarChartCodePreview.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback, Fragment } from 'react'; 2 | import { generateChartCode, CodeBlock, Code, CodeText, formatCode} from '../../../../utils/CodePreview'; 3 | 4 | const BarChartCodePreview = ({ name, data, children, ...codeProps }) => { 5 | 6 | const code = generateChartCode(`${name}`, codeProps, { 7 | dataKey: data !== undefined ? 'data' : undefined, 8 | children: children, 9 | defaults: {}, 10 | pkg: 'barchart', 11 | }) 12 | 13 | // References created by useRef itself do not trigger component rerenders, and at the start of the first render, it will be null 14 | // State must be modified to trigger any rerenders, so we use a callback ref to run some code 15 | // when React attaches or detaches a ref to a DOM node () 16 | const useCodeRef = (processNode) => { 17 | const [node, setNode] = useState(null); 18 | const setCodeRef = useCallback(newNode => { 19 | if (newNode) { 20 | // console.log("ref", node); // node = codeRef.current // 21 | setNode(processNode(newNode)); 22 | } 23 | }, []); 24 | return [node, setCodeRef] 25 | } 26 | 27 | const [codeRef, setCodeRef] = useCodeRef(node => node) 28 | 29 | // To reflect on every code change, we use useEffect to reassign the new codeRef on rerender 30 | useEffect(() => { 31 | console.log(codeRef) 32 | }, [codeRef]); 33 | 34 | return ( 35 | 36 | 37 | 38 | {code} 39 | 40 | 41 | 42 | 43 | {/* Something weird happening when putting export button here. 44 | Think it's setting codeRef in an infinite loop which leads to stack overflow lol 45 | 46 | */} 47 | 48 | 83 | 84 | 85 | ) 86 | } 87 | 88 | export default BarChartCodePreview 89 | -------------------------------------------------------------------------------- /src/Archives/charts/Histogram/Histogram.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | import * as d3 from "d3" 4 | 5 | 6 | import React, { useState, useEffect, useMemo, Fragment} from 'react'; 7 | import * as d3 from 'd3'; 8 | import PropTypes from "prop-types" 9 | import { useChartDimensions, accessorPropsType } from '../../../../utils/utils.js'; 10 | import Axis_noticks from "../../../ChartComponents/JSX/Axis_noticks.jsx" 11 | import Axis from "../../../ChartComponents/JSX/Axis.jsx" 12 | import Rectangle from "../../../ChartComponents/JSX/Rectangle.jsx" 13 | import Bars from "../../../ChartComponents/JSX/Bars.jsx" 14 | import Chart from "../../../ChartComponents/JSX/Chart.jsx" 15 | import { parseDate, dateAccessor, temperatureAccessor, humidityAccessor, getData } from '../../ScatterPlot/App' 16 | 17 | const Histogram = ({ data, xKey, yKey, xAxisLabel, yAxisLabel, height, width }) => { 18 | const gradientColors = ["#9980FA", "rgb(226, 222, 243)"] 19 | const Histogram = ({ data, xAccessor, label }) => { 20 | const gradientId = useUniqueId("Histogram-gradient") 21 | const [ref, dimensions] = useChartDimensions({ 22 | marginBottom: 77, 23 | }) 24 | 25 | const numberOfThresholds = 9 26 | 27 | const xScale = d3.scaleLinear() 28 | .domain(d3.extent(data, xAccessor)) 29 | .range([0, dimensions.boundedWidth]) 30 | .nice(numberOfThresholds) 31 | 32 | const binsGenerator = d3.bin() 33 | .domain(xScale.domain()) 34 | .value(xAccessor) 35 | .thresholds(xScale.ticks(numberOfThresholds)) 36 | 37 | const bins = binsGenerator(data) 38 | 39 | const yAccessor = d => d.length 40 | const yScale = d3.scaleLinear() 41 | .domain([0, d3.max(bins, yAccessor)]) 42 | .range([dimensions.boundedHeight, 0]) 43 | .nice() 44 | 45 | const barPadding = 2 46 | 47 | const xAccessorScaled = d => xScale(d.x0) + barPadding 48 | const yAccessorScaled = d => yScale(yAccessor(d)) 49 | const widthAccessorScaled = d => xScale(d.x1) - xScale(d.x0) - barPadding 50 | const heightAccessorScaled = d => dimensions.boundedHeight - yScale(yAccessor(d)) 51 | const keyAccessor = (d, i) => i 52 | 53 | return ( 54 |
55 | 56 | 57 | 63 | 64 | 70 | 76 | 85 | 86 |
87 | ) 88 | } 89 | 90 | Histogram.propTypes = { 91 | xAccessor: accessorPropsType, 92 | yAccessor: accessorPropsType, 93 | xLabel: PropTypes.string, 94 | yLabel: PropTypes.string, 95 | } 96 | 97 | Histogram.defaultProps = { 98 | xAccessor: d => d.x, 99 | yAccessor: d => d.y, 100 | } 101 | } 102 | 103 | export default Histogram -------------------------------------------------------------------------------- /src/Archives/charts/Histogram/HistogramCodePreview.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { generateChartCode, CodeBlock, Code } from '../../utils/CodePreview'; 3 | 4 | const HistogramCodePreview = ({ name, data, children, ...codeProps }) => { 5 | 6 | const code = generateChartCode(`${name}`, codeProps, { 7 | dataKey: data !== undefined ? 'data' : undefined, 8 | children: children, 9 | defaults: {}, 10 | pkg: 'histogram', 11 | }) 12 | 13 | return ( 14 | 15 | 16 | {code} 17 | 18 | ) 19 | } 20 | 21 | export default HistogramCodePreview -------------------------------------------------------------------------------- /src/Archives/charts/LineChartCodePreview.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function LineChartCodePreview(props) { 4 | return( 5 |
6 | Code Preview 7 |
8 | ) 9 | } 10 | 11 | export default LineChartCodePreview; -------------------------------------------------------------------------------- /src/Archives/charts/LineChartForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const LineChartForm = ({ 5 | data, 6 | xKey, 7 | yKey, 8 | xAxisLabel, 9 | yAxisLabel, 10 | height, 11 | width, 12 | handlers: { 13 | handleData, 14 | handleXKey, 15 | handleYKey, 16 | handleXAxisLabel, 17 | handleYAxisLabel, 18 | handleWidth, 19 | handleHeight 20 | } 21 | }) => { 22 | return ( 23 | // 24 | //
Chart Customizer Form
25 | //
26 |
{}}> 27 |
28 |

29 | This line chart currently only works for date info on the x-scale. 30 |

31 |
32 | 38 | 45 |

Please fill out this field.

46 |
47 | 48 |
49 | 55 | 61 |

Please fill out this field.

62 |
63 | 64 |
65 | 71 | 77 |

Please fill out this field.

78 |
79 | 80 |
81 | 87 | 93 |

Please fill out this field.

94 |
95 | 96 |
97 | 103 | 109 |

Please fill out this field.

110 |
111 | 112 |
113 | 119 | 125 |

Please fill out this field.

126 |
127 | 128 |
129 | 135 | 141 |

Please fill out this field.

142 |
143 |
144 |
145 | //
146 | //
147 | ); 148 | }; 149 | 150 | export default LineChartForm; 151 | -------------------------------------------------------------------------------- /src/Archives/charts/Scatterplot/ScatterPlotCodePreview.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { generateChartCode, CodeBlock, Code } from '../../../../utils/CodePreview'; 3 | 4 | const ScatterPlotCodePreview = ({ name, data, children, ...codeProps }) => { 5 | 6 | const code = generateChartCode(`${name}`, codeProps, { 7 | dataKey: data !== undefined ? 'data' : undefined, 8 | children: children, 9 | defaults: {}, 10 | pkg: 'barchart', 11 | }) 12 | 13 | return ( 14 | 15 | 16 | {code} 17 | 18 | ) 19 | } 20 | 21 | export default ScatterPlotCodePreview -------------------------------------------------------------------------------- /src/Archives/reducers/barPaddingSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: 2 5 | }; 6 | 7 | export const barPaddingSlice = createSlice({ 8 | name: 'barPadding', 9 | initialState, 10 | reducers: { 11 | changeBarPadding: (state, action) => { 12 | console.log('Changing barPadding'); 13 | state.value = +action.payload; 14 | } 15 | } 16 | }); 17 | 18 | // Action creators are generated for each case reducer function 19 | export const { changeBarPadding } = barPaddingSlice.actions; 20 | 21 | export default barPaddingSlice.reducer; 22 | -------------------------------------------------------------------------------- /src/Archives/reducers/dataSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { sampleData } from '../../utils/dummypenguinsdata'; 3 | //this slice/reducer should be the same as what handleData fn does 4 | 5 | const initialState = { 6 | value: sampleData 7 | }; 8 | 9 | export const dataSlice = createSlice({ 10 | name: 'data', 11 | initialState, 12 | reducers: { 13 | // Redux Toolkit allows us to write "mutating" logic in reducers. It 14 | // doesn't actually mutate the state because it uses the Immer library, 15 | // which detects changes to a "draft state" and produces a brand new 16 | // immutable state based off those changes 17 | // increment: (state) => { 18 | // state.value += 1; 19 | // }, 20 | // decrement: (state) => { 21 | // state.value -= 1; 22 | // }, 23 | // incrementByAmount: (state, action) => { 24 | // state.value += action.payload; 25 | // }, 26 | changeData: (state, action) => { 27 | console.log('changing data'); 28 | state.value = JSON.parse(action.payload); 29 | } 30 | } 31 | }); 32 | 33 | // Action creators are generated for each case reducer function 34 | export const { changeData } = dataSlice.actions; 35 | 36 | export default dataSlice.reducer; 37 | -------------------------------------------------------------------------------- /src/Archives/reducers/heightSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: 500 5 | }; 6 | 7 | export const heightSlice = createSlice({ 8 | name: 'height', 9 | initialState, 10 | reducers: { 11 | changeHeight: (state, action) => { 12 | if (+action.payload < 100) { 13 | console.log( 14 | 'Value must not be less than 100 px. Resetting to default.' 15 | ); 16 | action.payload = 500; 17 | console.log(action.payload); 18 | state.value = action.payload; 19 | return; 20 | } 21 | console.log('Changing height'); 22 | state.value = +action.payload; 23 | } 24 | } 25 | }); 26 | 27 | // Action creators are generated for each case reducer function 28 | export const { changeHeight } = heightSlice.actions; 29 | 30 | export default heightSlice.reducer; 31 | -------------------------------------------------------------------------------- /src/Archives/reducers/nameSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: '' 5 | }; 6 | 7 | export const nameSlice = createSlice({ 8 | name: 'name', 9 | initialState, 10 | reducers: { 11 | changeName: (state, action) => { 12 | console.log('Changing Chart name'); 13 | state.value = action.payload; 14 | } 15 | } 16 | }); 17 | 18 | // Action creators are generated for each case reducer function 19 | export const { changeName } = nameSlice.actions; 20 | 21 | export default nameSlice.reducer; 22 | -------------------------------------------------------------------------------- /src/Archives/reducers/radiusSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: 5 5 | }; 6 | 7 | export const radiusSlice = createSlice({ 8 | name: 'radius', 9 | initialState, 10 | reducers: { 11 | changeRadius: (state, action) => { 12 | if (+action.payload < 1) { 13 | console.log('Value must not be less than 1. Resetting to default.'); 14 | action.payload = 5; 15 | console.log(action.payload); 16 | state.value = action.payload; 17 | return; 18 | } 19 | console.log('Changing radius'); 20 | state.value = +action.payload; 21 | } 22 | } 23 | }); 24 | 25 | // Action creators are generated for each case reducer function 26 | export const { changeRadius } = radiusSlice.actions; 27 | 28 | export default radiusSlice.reducer; 29 | -------------------------------------------------------------------------------- /src/Archives/reducers/thresholdsSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: 9 5 | }; 6 | 7 | export const thresholdsSlice = createSlice({ 8 | name: 'thresholds', 9 | initialState, 10 | reducers: { 11 | changeThresholds: (state, action) => { 12 | console.log('Changing thresholds'); 13 | state.value = +action.payload; 14 | } 15 | } 16 | }); 17 | 18 | // Action creators are generated for each case reducer function 19 | export const { changeThresholds } = thresholdsSlice.actions; 20 | 21 | export default thresholdsSlice.reducer; 22 | -------------------------------------------------------------------------------- /src/Archives/reducers/widthSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: 500 5 | }; 6 | 7 | export const widthSlice = createSlice({ 8 | name: 'width', 9 | initialState, 10 | reducers: { 11 | changeWidth: (state, action) => { 12 | if (+action.payload < 100) { 13 | console.log( 14 | 'Value must not be less than 100 px. Resetting to default.' 15 | ); 16 | action.payload = 500; 17 | console.log(action.payload); 18 | state.value = action.payload; 19 | return; 20 | } 21 | console.log('Changing width'); 22 | state.value = +action.payload; 23 | } 24 | } 25 | }); 26 | 27 | // Action creators are generated for each case reducer function 28 | export const { changeWidth } = widthSlice.actions; 29 | 30 | export default widthSlice.reducer; 31 | -------------------------------------------------------------------------------- /src/Archives/reducers/xAxisLabelSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: 'X-axis: Species' 5 | }; 6 | 7 | export const xAxisLabelSlice = createSlice({ 8 | name: 'xAxisLabel', 9 | initialState, 10 | reducers: { 11 | changeXAxisLabel: (state, action) => { 12 | console.log('Changing X Axis Label'); 13 | state.value = action.payload; 14 | } 15 | } 16 | }); 17 | 18 | // Action creators are generated for each case reducer function 19 | export const { changeXAxisLabel } = xAxisLabelSlice.actions; 20 | 21 | export default xAxisLabelSlice.reducer; 22 | -------------------------------------------------------------------------------- /src/Archives/reducers/xKeySlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: '' 5 | }; 6 | 7 | export const xKeySlice = createSlice({ 8 | name: 'xKey', 9 | initialState, 10 | reducers: { 11 | changeXKey: (state, action) => { 12 | console.log('Changing X Key'); 13 | state.value = action.payload; 14 | } 15 | } 16 | }); 17 | 18 | // Action creators are generated for each case reducer function 19 | export const { changeXKey } = xKeySlice.actions; 20 | 21 | export default xKeySlice.reducer; 22 | -------------------------------------------------------------------------------- /src/Archives/reducers/yAxisLabelSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: 'Y-axis: Body Mass' 5 | }; 6 | 7 | export const yAxisLabelSlice = createSlice({ 8 | name: 'yAxisLabel', 9 | initialState, 10 | reducers: { 11 | changeYAxisLabel: (state, action) => { 12 | console.log('Changing Y Axis Label'); 13 | state.value = action.payload; 14 | } 15 | } 16 | }); 17 | 18 | // Action creators are generated for each case reducer function 19 | export const { changeYAxisLabel } = yAxisLabelSlice.actions; 20 | 21 | export default yAxisLabelSlice.reducer; 22 | -------------------------------------------------------------------------------- /src/Archives/reducers/yKeySlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | value: '' 5 | }; 6 | 7 | export const yKeySlice = createSlice({ 8 | name: 'yKey', 9 | initialState, 10 | reducers: { 11 | changeYKey: (state, action) => { 12 | console.log('Changing Y Key'); 13 | state.value = action.payload; 14 | } 15 | } 16 | }); 17 | 18 | // Action creators are generated for each case reducer function 19 | export const { changeYKey } = yKeySlice.actions; 20 | 21 | export default yKeySlice.reducer; 22 | -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/Axis.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | import * as d3 from 'd3' 4 | import { dimensionsPropsType } from "./utils"; 5 | import { useChartDimensions } from "./Chart"; 6 | 7 | const axisComponentsByDimension = { 8 | x: AxisHorizontal, 9 | y: AxisVertical, 10 | } 11 | const Axis = ({ dimension, ...props }) => { 12 | const dimensions = useChartDimensions() 13 | const Component = axisComponentsByDimension[dimension] 14 | if (!Component) return null 15 | 16 | return ( 17 | 21 | ) 22 | } 23 | 24 | Axis.propTypes = { 25 | dimension: PropTypes.oneOf(["x", "y"]), 26 | scale: PropTypes.func, 27 | label: PropTypes.string, 28 | formatTick: PropTypes.func, 29 | } 30 | 31 | Axis.defaultProps = { 32 | dimension: "x", 33 | scale: null, 34 | formatTick: d3.format(","), 35 | } 36 | 37 | export default Axis 38 | 39 | 40 | function AxisHorizontal ({ dimensions, label, formatTick, scale, ...props }) { 41 | const numberOfTicks = dimensions.boundedWidth < 600 42 | ? dimensions.boundedWidth / 100 43 | : dimensions.boundedWidth / 250 44 | 45 | const ticks = scale.ticks(numberOfTicks) 46 | 47 | return ( 48 | 49 | 53 | 54 | {ticks.map((tick, i) => ( 55 | 60 | { formatTick(tick) } 61 | 62 | ))} 63 | 64 | {label && ( 65 | 69 | { label } 70 | 71 | )} 72 | 73 | ) 74 | } 75 | 76 | function AxisVertical ({ dimensions, label, formatTick, scale, ...props }) { 77 | const numberOfTicks = dimensions.boundedHeight / 70 78 | 79 | const ticks = scale.ticks(numberOfTicks) 80 | 81 | return ( 82 | 83 | 87 | 88 | {ticks.map((tick, i) => ( 89 | 94 | { formatTick(tick) } 95 | 96 | ))} 97 | 98 | {label && ( 99 | 105 | { label } 106 | 107 | )} 108 | 109 | ) 110 | } -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/AxisHorizontal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name: AxisHorizontal 3 | * @description: produces a modularized template for the Horizontal Axis 4 | * @param: (object with keys domain: number[] and range: number[]), pixelsPerTick: number 5 | * @returns: A rendering of the X-Axis 6 | * @author: Antonio Ayala, Sophia Chiao 7 | */ 8 | 9 | 10 | // import modules and libraries 11 | import React, { useMemo } from 'react'; 12 | import { scaleLinear, domain, range, ticks } from 'd3'; 13 | 14 | export const AxisHorizontal = ({ 15 | domain = [0, 100], 16 | range = [10, 290], 17 | pixelsPerTick = 30, 18 | }) => { 19 | // creating the tick marks for the X-Axis (Axis Bottom) utilizing the React Hook useMemo 20 | // useMemo returns a memoized value https://dmitripavlutin.com/react-usememo-hook/ 21 | const ticks = useMemo(() => { 22 | // set the xScale using d3's scaleLinear() method 23 | // to create a visual scale point. This method is used to transform data values into 24 | // visual variables. 25 | // .domain is generating every intermediate tick mark 26 | // .range is setting the range at which these tick marks will reside 27 | const xScale = scaleLinear() 28 | .domain(domain) 29 | .range(range); 30 | 31 | // declaring variable width to determine the width of the graph 32 | const width = range[1] - range[0]; 33 | // console.log("width: ", width); 34 | 35 | // determine how many ticks to use/can fit in the axis based on range -> width/pixelsPerTick 36 | const numberOfTicksTarget = Math.max(1, Math.floor(width / pixelsPerTick)); 37 | // console.log("numberOfTicksTarget: ", numberOfTicksTarget); 38 | 39 | return ( 40 | xScale 41 | // using d3's .ticks() method to generate ticks, quantity of ticks based on input value 42 | .ticks(numberOfTicksTarget) 43 | // creating the minor label 44 | .map((value) => ({ 45 | value, 46 | xOffset: xScale(value), 47 | })) 48 | ); 49 | }, [ 50 | // whenever domain and range change, update tickmarks 51 | domain.join("-"), 52 | range.join("-"), 53 | ]); 54 | 55 | return ( 56 | 57 | 69 | 70 | {ticks.map(({ value, xOffset }) => ( 71 | 75 | 76 | 84 | {value} 85 | 86 | 87 | ))} 88 | 89 | ); 90 | }; -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/AxisVertical.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name: AxisVertical 3 | * @description: produces a modularized template for the Vertical Axis 4 | * @param: (object with keys domain: number[] and range: number[]), pixelsPerTick: number 5 | * @returns: A rendering of the Y-Axis 6 | * @author: Antonio Ayala, Sophia Chiao 7 | */ 8 | 9 | 10 | // import modules and libraries 11 | import React, { useMemo } from 'react'; 12 | import { scaleLinear, domain, range, ticks, axisLeft, scale } from 'd3'; 13 | 14 | export const AxisVertical = ({ 15 | domain = [0, 100], 16 | range = [10, 290], 17 | pixelsPerTick = 30, 18 | }) => { 19 | // creating the tick marks for the X-Axis (Axis Bottom) utilizing the React Hook useMemo 20 | // useMemo returns a memoized value https://dmitripavlutin.com/react-usememo-hook/ 21 | const ticks = useMemo(() => { 22 | // set the yScale using d3's scaleLinear() method 23 | // to create a visual scale point. This method is used to transform data values into 24 | // visual variables. 25 | // .domain is generating every intermediate tick mark 26 | // .range is setting the range at which these tick marks will reside 27 | const yScale = scaleLinear().domain(domain.reverse()).range(range) 28 | 29 | // declaring variable height to determine the height of the graph 30 | const height = range[1] - range[0]; 31 | 32 | // determine how many ticks to use/can fit in the axis based on range -> height/pixelsPerTick 33 | const numberOfTicksTarget = Math.max(1, Math.floor(height / pixelsPerTick)); 34 | 35 | return ( 36 | yScale 37 | // using d3's .ticks() method to generate ticks, quantity of ticks based on input value 38 | .ticks(numberOfTicksTarget) 39 | // creating the minor label 40 | .map((value) => ({ 41 | value: value, 42 | yOffset: yScale(value), 43 | })) 44 | ); 45 | }, [ 46 | // whenever domain and range change, update tickmarks 47 | domain.join("-"), 48 | range.join("-"), 49 | ]); 50 | 51 | return ( 52 | 53 | 54 | 62 | 63 | {ticks.map(({ value, yOffset }) => ( 64 | 65 | 70 | 78 | {value} 79 | 80 | 81 | ))} 82 | 83 | 84 | ); 85 | }; -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/Axis_comments.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @name: Axis 3 | * @description: produces a modularized template for the Horizontal and Vertical Axes 4 | * @param: 5 | * @returns: 6 | * @author: Antonio Ayala, Sophia Chiao 7 | */ 8 | 9 | // import modules and libraries 10 | import React from "react" 11 | import PropTypes from "prop-types" // This is essentially typescript but REACT 12 | import * as d3 from 'd3' 13 | import { useChartDimensions } from './useChartDimensions_comments' 14 | 15 | // declaring a const axisComponentsByDimension set where the x and y keys are set to the 16 | // functions AxisHorizontal and AxisVertical through JavaScript/React Hoisting 17 | const axisComponentsByDimension = { 18 | x: AxisHorizontal, 19 | y: AxisVertical, 20 | } 21 | 22 | // declaring a const Axis that returns the invocation of either AxisHorizontal or AxisVertical 23 | // dimension : "x" or "y" 24 | const Axis = ({ dimension, ...props }) => { 25 | 26 | // returns the new dimensions of the window 27 | const dimensions = useChartDimensions() 28 | 29 | // setting the Component variable to be the function AxisHorizontal or AxisVertical 30 | const Component = axisComponentsByDimension[dimension] 31 | 32 | // if Component does not exist, return null 33 | if (!Component) return null 34 | 35 | return ( 36 | // Rendering the AxisHorizontal / AxisVertical 37 | 41 | ) 42 | } 43 | 44 | // THIS CAN BE REPLACED BY TYPESCRIPT 45 | Axis.propTypes = { 46 | dimension: PropTypes.oneOf(["x", "y"]), 47 | scale: PropTypes.func, 48 | label: PropTypes.string, 49 | formatTick: PropTypes.func, 50 | } 51 | 52 | // defaultProps is a React component property that allows you to set default values for the props argument 53 | Axis.defaultProps = { 54 | dimension: "x", 55 | scale: null, 56 | formatTick: d3.format(","), 57 | } 58 | 59 | // Exporting the Axis function 60 | export default Axis 61 | 62 | /******************************************************************************************************************/ 63 | /******************** Below are helper functions for Axis to render the specified X- or Y- Axis *******************/ 64 | /******************************************************************************************************************/ 65 | 66 | // Creates the rendering of the Horizontal Axis 67 | // input: dimensions, label, formatTick, scale, data, ...props 68 | // output: rendering of the X-Axis 69 | function AxisHorizontal ({ dimensions, label, formatTick, scale, data, ...props }) { 70 | const numberOfTicks = dimensions.boundedWidth < 600 71 | ? dimensions.boundedWidth / 100 72 | : dimensions.boundedWidth / 250 73 | 74 | // const ticks = scale.ticks(numberOfTicks) 75 | // console.log(scale) 76 | // const ticks = axis(scale).tickValues([data.map((d) => d.product)]) 77 | 78 | return ( 79 | 80 | 84 | 85 | {/* {ticks.map((tick, i) => ( 86 | 91 | { formatTick(tick) } 92 | 93 | ))} */} 94 | 95 | {label && ( 96 | 100 | { label } 101 | 102 | )} 103 | 104 | ) 105 | } 106 | 107 | function AxisVertical ({ dimensions, label, formatTick, scale, ...props }) { 108 | const numberOfTicks = dimensions.boundedHeight / 70 109 | 110 | const ticks = scale.ticks(numberOfTicks) 111 | 112 | return ( 113 | 114 | 118 | 119 | {ticks.map((tick, i) => ( 120 | 125 | { formatTick(tick) } 126 | 127 | ))} 128 | 129 | {label && ( 130 | 136 | { label } 137 | 138 | )} 139 | 140 | ) 141 | } 142 | -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/Bars.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | import * as d3 from 'd3' 4 | import { accessorPropsType } from './Archive/accessorPropsType' 5 | import { useAccessor } from './Archive/useAccessor' 6 | 7 | const Bars = ({ data, keyAccessor, xAccessor, yAccessor, widthAccessor, heightAccessor, ...props }) => ( 8 | 9 | {data.map((d, i) => ( 10 | 18 | ))} 19 | 20 | ) 21 | 22 | Bars.propTypes = { 23 | data: PropTypes.array, 24 | keyAccessor: accessorPropsType, 25 | xAccessor: accessorPropsType, 26 | yAccessor: accessorPropsType, 27 | widthAccessor: accessorPropsType, 28 | heightAccessor: accessorPropsType, 29 | } 30 | 31 | Bars.defaultProps = { 32 | } 33 | 34 | export default Bars -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/Chart.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext } from "react" 2 | import { dimensionsPropsType } from "./utils" 3 | 4 | import "./Chart.css" 5 | 6 | const ChartContext = createContext() 7 | export const useChartDimensions = () => useContext(ChartContext) 8 | 9 | const Chart = ({ dimensions, children }) => ( 10 | 11 | 12 | 13 | { children } 14 | 15 | 16 | 17 | ) 18 | 19 | Chart.propTypes = { 20 | dimensions: dimensionsPropsType 21 | } 22 | 23 | Chart.defaultProps = { 24 | dimensions: {} 25 | } 26 | 27 | export default Chart -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/Chart_comments.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @pr 3 | * @name: Chart 4 | * @description: produces the complete Chart with axes 5 | * @param: 6 | * @returns: 7 | * @author: Antonio Ayala, Sophia Chiao 8 | */ 9 | 10 | 11 | // import modules and libraries 12 | import React, { createContext, useContext } from "react"; 13 | import { dimensionsPropsType } from './dimensionsPropsType'; 14 | import "./Chart.css"; 15 | 16 | // setting the return of invoking createContext() to ChartContext 17 | // createContext lets us pass a value deep into the component tree without explicitly threading it through every component. 18 | // the return of invoking createContext() returns an object with { Provider, Consumer } 19 | const ChartContext = createContext(); 20 | 21 | // useContext is a React Hook that accepts a context object (value returned from React.createContext) and returns 22 | // the current context value for that context. 23 | // In this case, it would be null 24 | export const useChartDimensions = () => useContext(ChartContext); 25 | 26 | 27 | const Chart = ({ dimensions, children }) => ( 28 | // the Context.Provider is an effect of createContext() and allows the designated properties to be passed down 29 | // Any component can read it, no matter how deep it is. 30 | // In this case, we are passing to other components dimensions 31 | 32 | 33 | 34 | { children } 35 | 36 | 37 | 38 | ) 39 | 40 | Chart.propTypes = { 41 | dimensions: dimensionsPropsType 42 | } 43 | 44 | Chart.defaultProps = { 45 | dimensions: {} 46 | } 47 | 48 | export default Chart 49 | -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/CodePreview.js: -------------------------------------------------------------------------------- 1 | import forOwn from 'lodash/forOwn'; 2 | import isPlainObject from 'lodash/isPlainObject'; 3 | import isArray from 'lodash/isArray'; 4 | import isString from 'lodash/isString'; 5 | import isNumber from 'lodash/isNumber'; 6 | import isBoolean from 'lodash/isBoolean'; 7 | import dedent from 'dedent-js'; 8 | import styled from 'styled-components'; 9 | 10 | export const indent = (content, spaces = 8) => 11 | content 12 | .split('\n') 13 | .map((line, i) => { 14 | if (i === 0) return line; 15 | return `${' '.repeat(spaces)}${line}`; 16 | }) 17 | .join('\n'); 18 | 19 | /* convert all inputs to json 🙃 */ 20 | export const toJson = (value) => { 21 | const jsonString = JSON.stringify(value, null, 4); 22 | const normalized = jsonString 23 | .replace(/^(\s+)"([a-z]{1}[a-z]*)"\: /gim, (_match, space, key) => { 24 | return `${space}${key}: `; 25 | }) 26 | .replace(/"/gm, `'`); 27 | 28 | if (normalized.length < 80) { 29 | return normalized.replace(/\n/gm, ' ').replace(/\s{2,}/g, ' '); 30 | } 31 | return indent(normalized); 32 | }; 33 | 34 | export const generateChartCode = ( 35 | name, 36 | props, 37 | { dataKey, children, defaults, pkg } 38 | ) => { 39 | const properties = []; 40 | let args = ''; 41 | 42 | if (dataKey !== undefined) { 43 | properties.push(`${dataKey}={${dataKey}}`); 44 | args = `{ ${dataKey} /* see ${dataKey} from PropsData file */ }`; 45 | } 46 | 47 | forOwn(props, (_value, key) => { 48 | if (_value === undefined) return; 49 | if (defaults && defaults[key] === _value) return; 50 | if (key === 'theme') return; 51 | 52 | let value; 53 | if (isPlainObject(_value)) { 54 | value = `{${toJson(_value)}}`; 55 | } else if (isArray(_value)) { 56 | value = `{${toJson(_value)}}`; 57 | } else if (isString(_value)) { 58 | value = `"${_value}"`; 59 | } else if (isBoolean(_value)) { 60 | value = `{${_value ? 'true' : 'false'}}`; 61 | } else if (isNumber(_value)) { 62 | value = `{${_value}}`; 63 | } else if (typeof _value === 'function') { 64 | value = `{${indent(dedent(_value.toString()), 8)}}`; 65 | } else if (_value === null) { 66 | value = `{null}`; 67 | } else { 68 | value = _value; 69 | } 70 | 71 | properties.push(`${key}=${value}`); 72 | }); 73 | 74 | const install = `// npm install @d3act @d3act/${pkg}`; 75 | 76 | const imports = [name, ...children.map((c) => c)].map( 77 | (i) => `import { ${i} } from 'd3act/components'` 78 | ); 79 | 80 | let warning = ''; 81 | if (name) { 82 | warning = [ 83 | ``, 84 | `// Before use, remember to npm i all dependencies`, 85 | `// and the @d3act component library to use your charts,`, 86 | `// otherwise, no charts will be rendered.`, 87 | `// Copy the following code to your component file`, 88 | `// along with your PropsData.txt file .` 89 | ].join('\n'); 90 | } 91 | 92 | return `// install (please make sure versions match peerDependencies) 93 | ${install} 94 | ${imports.join('\n')} 95 | ${warning} 96 | const My${name} = (${args}) => ( 97 | <${name} 98 | ${properties.join('\n ')} 99 | /> 100 | )`; 101 | }; 102 | 103 | //just using styled components here only for testing html preview 104 | export const CodeBlock = styled.pre` 105 | margin: 0; 106 | font-size: 0.8rem; 107 | line-height: 1.7; 108 | padding: 12px 20px; 109 | `; 110 | 111 | export const Code = styled.div` 112 | position: absolute; 113 | top: 46px; 114 | bottom: 0; 115 | width: 100%; 116 | overflow: auto; 117 | `; 118 | -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/accessorPropsType.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react" 3 | 4 | 5 | export const accessorPropsType = ( 6 | PropTypes.oneOfType([ 7 | PropTypes.func, 8 | PropTypes.number, 9 | ]) 10 | ) -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/combineChartDimensions.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @pr 3 | * @name: combineChartsDimensions 4 | * @description: creates a new chart dimension object based on window changes 5 | * @param: 6 | * @returns: 7 | * @author: Antonio Ayala, Sophia Chiao 8 | */ 9 | 10 | // import modules and libraries 11 | 12 | export const combineChartDimensions = dimensions => { 13 | let parsedDimensions = { 14 | marginTop: 40, 15 | marginRight: 30, 16 | marginBottom: 40, 17 | marginLeft: 75, 18 | ...dimensions, 19 | } 20 | 21 | return { 22 | ...parsedDimensions, 23 | boundedHeight: Math.max(parsedDimensions.height - parsedDimensions.marginTop - parsedDimensions.marginBottom, 0), 24 | boundedWidth: Math.max(parsedDimensions.width - parsedDimensions.marginLeft - parsedDimensions.marginRight, 0), 25 | } 26 | } -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/createFiles.js: -------------------------------------------------------------------------------- 1 | // Creates all component files (but not the full application files) and places them in a "components" directory 2 | const createFiles = () => { 3 | let dir = path; 4 | // if (exportAppBool === false) { 5 | // if (!dir.match(/components|\*$/)) { 6 | // if (window.api.existsSync(`${dir}/src`)) { 7 | // dir = `${dir}/src`; 8 | // } 9 | // dir = `${dir}/components`; 10 | // if (!window.api.existsSync(dir)) { 11 | // window.api.mkdirSync(dir); 12 | // } 13 | // } 14 | // } else if (exportAppBool) { 15 | // if (!dir.match(/${appName}|\*$/)) { 16 | // dir = `${dir}/${appName}/src/components`; 17 | // } 18 | // } 19 | const promises = []; 20 | components.forEach((component) => { 21 | const newPromise = new Promise((resolve, reject) => { 22 | window.api.writeFileSync( 23 | `${dir}/${component.name}.jsx`, 24 | window.api.formatCode(component.code), 25 | (err) => { 26 | if (err) return reject(err.message); 27 | return resolve(path); 28 | } 29 | ); 30 | }); 31 | promises.push(newPromise); 32 | }); 33 | return Promise.all(promises); 34 | }; 35 | 36 | export default createFiles; 37 | 38 | //prop types 39 | // components: any, 40 | // path: string, 41 | // appName: string, 42 | // exportAppBool: boolean 43 | -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/dimensionsPropsType.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @pr 3 | * @name: dimensionsPropsType 4 | * @description: sets the type properties for dimensions 5 | * @param: 6 | * @returns: 7 | * @author: Antonio Ayala, Sophia Chiao 8 | */ 9 | 10 | // import modules and libraries 11 | import PropTypes from 'prop-types' 12 | 13 | 14 | export const dimensionsPropsType = ( 15 | PropTypes.shape({ 16 | height: PropTypes.number, 17 | width: PropTypes.number, 18 | marginTop: PropTypes.number, 19 | marginRight: PropTypes.number, 20 | marginBottom: PropTypes.number, 21 | marginLeft: PropTypes.number, 22 | }) 23 | ) -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/exportButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useCallback, useEffect } from 'react'; 2 | import StateContext from '../../context/context'; 3 | import List from '@material-ui/core/List'; 4 | import ListItem from '@material-ui/core/ListItem'; 5 | import ListItemText from '@material-ui/core/ListItemText'; 6 | import GetAppIcon from '@material-ui/icons/GetApp'; 7 | import Button from '@material-ui/core/Button'; 8 | import exportProject from '../../utils/exportProject.util'; 9 | import createModal from './createModal'; 10 | import { styleContext } from '../../containers/AppContainer'; 11 | 12 | export default function ExportButton() { 13 | const [modal, setModal] = useState(null); 14 | const [state] = useContext(StateContext); 15 | 16 | const genOptions = [ 17 | 'Export components', 18 | 'Export components with application files' 19 | ]; 20 | let genOption = 0; 21 | 22 | // Closes out the open modal 23 | const closeModal = () => setModal(''); 24 | 25 | const showGenerateAppModal = () => { 26 | const children = ( 27 | 28 | {genOptions.map((option, i) => ( 29 | chooseGenOptions(i)} 33 | style={{ 34 | border: '1px solid #3f51b5', 35 | marginBottom: '2%', 36 | marginTop: '5%' 37 | }} 38 | > 39 | 40 | 41 | ))} 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | let testchecked = 0; 49 | // helper function called by showGenerateAppModal 50 | // this function will prompt the user to choose an app directory once they've chosen their export option 51 | const chooseGenOptions = (genOpt) => { 52 | // set export option: 0 --> export only components, 1 --> export full project 53 | genOption = genOpt; 54 | window.api.chooseAppDir(); 55 | testchecked = document.getElementById('tests').checked; 56 | closeModal(); 57 | }; 58 | 59 | // removes all listeners for the app_dir_selected event 60 | // this is important because otherwise listeners will pile up and events will trigger multiple events 61 | window.api.removeAllAppDirChosenListeners(); 62 | 63 | // add listener for when an app directory is chosen 64 | // when a directory is chosen, the callback will export the project to the chosen folder 65 | // Note: this listener is imported from the main process via preload.js 66 | window.api.addAppDirChosenListener((path) => { 67 | exportProject( 68 | path, 69 | state.name 70 | ? state.name 71 | : 'New_ReacType_Project_' + Math.ceil(Math.random() * 99).toString(), 72 | genOption, 73 | testchecked, 74 | state.projectType, 75 | state.components, 76 | state.rootComponents 77 | ); 78 | }); 79 | 80 | // setModal( 81 | // createModal({ 82 | // closeModal, 83 | // children, 84 | // message: 'Choose export preference:', 85 | // primBtnLabel: null, 86 | // primBtnAction: null, 87 | // secBtnAction: null, 88 | // secBtnLabel: null, 89 | // open: true 90 | // }) 91 | // ); 92 | }; 93 | 94 | const exportKeyBind = useCallback((e) => { 95 | //Export Project 96 | (e.key === 'e' && e.metaKey) || (e.key === 'e' && e.ctrlKey) 97 | ? showGenerateAppModal() 98 | : ''; 99 | }, []); 100 | 101 | useEffect(() => { 102 | document.addEventListener('keydown', exportKeyBind); 103 | return () => { 104 | document.removeEventListener('keydown', exportKeyBind); 105 | }; 106 | }, []); 107 | return ( 108 |
109 | 118 | {modal} 119 |
120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/useAccessor.jsx: -------------------------------------------------------------------------------- 1 | export const useAccessor = (accessor, d, i) => ( 2 | typeof accessor == "function" ? accessor(d, i) : accessor 3 | ) -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/useChartDimensions_comments.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @pr 3 | * @name: useChartDimensions 4 | * @description: creates a new chart dimension object based on window changes 5 | * @param: passedSettings, and object with a similar model structure as combineChartDimensions' parsedDimensions 6 | * @returns: returns the size of the new window 7 | * @author: Antonio Ayala, Sophia Chiao 8 | */ 9 | 10 | // import modules and libraries 11 | import { combineChartDimensions } from './combineChartDimensions' 12 | import { useRef, useState, useEffect } from 'react'; 13 | 14 | 15 | // custom React Hook useChartDimensions that helps keep charts responsive and automatically updates any dimensions when 16 | // window is resized 17 | export const useChartDimensions = passedSettings => { 18 | 19 | // React hook that accepts an argument as the initial value and returns a reference (aka ref). 20 | // A reference is an object having a special property: current, which allows access to the updated value through 21 | // ref.current 22 | const ref = useRef() 23 | 24 | // invoking the combineChartDimensions passing in setting preferences and returns an object stored as dimensions 25 | const dimensions = combineChartDimensions(passedSettings) 26 | 27 | // setting the state for the width and height of the windows 28 | const [width, changeWidth] = useState(0) 29 | const [height, changeHeight] = useState(0) 30 | 31 | // React Hook useEffect() tells the React Component to run on the first render 32 | // https://www.w3schools.com/react/react_useeffect.asp 33 | useEffect(() => { 34 | 35 | // if the properties width and height are defined return nothing 36 | if (dimensions.width && dimensions.height) return 37 | 38 | // setting the ref.current to element 39 | const element = ref.current 40 | 41 | // creating a new instance of ResizeObserver which reports changes to the dimensions of an Element's 42 | // content or border box, or the bounding box of an SVGElement 43 | // Syntax: new ResizeObserver(callback) --> where the callback 44 | // accepts an array of ResizeObserverEntry objects that can be used to access the new dimensions 45 | // of the element after each change 46 | // https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver 47 | const resizeObserver = new ResizeObserver(entries => { 48 | 49 | // edge cases to ensure that entries is an array and has a length 50 | if (!Array.isArray(entries)) return 51 | if (!entries.length) return 52 | 53 | // initializing a constant entry set to the zero index of entries 54 | const entry = entries[0] 55 | 56 | // if the width and height state are not updated, 57 | // using the useState React Hook, to set the width and height to the correct values 58 | if (width != entry.contentRect.width) changeWidth(entry.contentRect.width) 59 | if (height != entry.contentRect.height) changeHeight(entry.contentRect.height) 60 | 61 | // using the observe() method in ResizeObserver to observe the specified element 62 | resizeObserver.observe(element) 63 | 64 | // return an anonymous function that uses the unobserve() method in ResizeObserver to 65 | // end the observing of an element 66 | return () => resizeObserver.unobserve(element) 67 | }, []) 68 | }) 69 | // create a new object with new dimensions 70 | // const newSettings = combineChartDimensions({ 71 | // ...dimensions, 72 | // width: dimensions.width || width, 73 | // height: dimensions.height || height, 74 | // }) 75 | 76 | // initial conditions, useEffect will invoke when ref or newSettings changes 77 | // return [ref, newSettings] 78 | return [ref, combineChartDimensions] 79 | } -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/useUniqueId.js: -------------------------------------------------------------------------------- 1 | let lastId = 0 2 | export const useUniqueId = (prefix="") => { 3 | lastId++ 4 | return [prefix, lastId].join("-") 5 | } -------------------------------------------------------------------------------- /src/Archives/utilities/Archive/utils.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { useEffect, useState, useRef } from "react" 3 | import ResizeObserver from "resize-observer-polyfill" 4 | 5 | export const accessorPropsType = ( 6 | PropTypes.oneOfType([ 7 | PropTypes.func, 8 | PropTypes.number, 9 | ]) 10 | ) 11 | 12 | export const useAccessor = (accessor, d, i) => ( 13 | typeof accessor == "function" ? accessor(d, i) : accessor 14 | ) 15 | 16 | export const dimensionsPropsType = ( 17 | PropTypes.shape({ 18 | height: PropTypes.number, 19 | width: PropTypes.number, 20 | marginTop: PropTypes.number, 21 | marginRight: PropTypes.number, 22 | marginBottom: PropTypes.number, 23 | marginLeft: PropTypes.number, 24 | }) 25 | ) 26 | 27 | export const combineChartDimensions = dimensions => { 28 | let parsedDimensions = { 29 | marginTop: 40, 30 | marginRight: 30, 31 | marginBottom: 40, 32 | marginLeft: 75, 33 | ...dimensions, 34 | } 35 | 36 | return { 37 | ...parsedDimensions, 38 | boundedHeight: Math.max(parsedDimensions.height - parsedDimensions.marginTop - parsedDimensions.marginBottom, 0), 39 | boundedWidth: Math.max(parsedDimensions.width - parsedDimensions.marginLeft - parsedDimensions.marginRight, 0), 40 | } 41 | } 42 | 43 | export const useChartDimensions = passedSettings => { 44 | const ref = useRef() 45 | const dimensions = combineChartDimensions(passedSettings) 46 | 47 | if (dimensions.width && dimensions.height) return [ref, dimensions] 48 | 49 | const [width, changeWidth] = useState(0) 50 | const [height, changeHeight] = useState(0) 51 | 52 | useEffect(() => { 53 | const element = ref.current 54 | const resizeObserver = new ResizeObserver(entries => { 55 | if (!Array.isArray(entries)) return 56 | if (!entries.length) return 57 | 58 | const entry = entries[0] 59 | 60 | if (width != entry.contentRect.width) changeWidth(entry.contentRect.width) 61 | if (height != entry.contentRect.height) changeHeight(entry.contentRect.height) 62 | }) 63 | 64 | resizeObserver.observe(element) 65 | 66 | return () => resizeObserver.unobserve(element) 67 | }, []) 68 | 69 | const newSettings = combineChartDimensions({ 70 | ...dimensions, 71 | width: dimensions.width || width, 72 | height: dimensions.height || height, 73 | }) 74 | 75 | return [ref, newSettings] 76 | } 77 | 78 | let lastId = 0 79 | export const useUniqueId = (prefix="") => { 80 | lastId++ 81 | return [prefix, lastId].join("-") 82 | } -------------------------------------------------------------------------------- /src/Archives/utilities/exportProject.js: -------------------------------------------------------------------------------- 1 | import createApplicationUtil from './createApplication.util'; 2 | import createFiles from './createFiles.util'; 3 | 4 | // When a user clicks the "Export project" function from the app, this function is invoked 5 | const exportProject = ( 6 | path, 7 | appName, 8 | genOption, 9 | tests, 10 | projectType, 11 | components, 12 | rootComponents 13 | ) => { 14 | // Create fully functional classic react application 15 | if (genOption === 1 && projectType === 'Classic React') { 16 | createApplicationUtil({ 17 | path, 18 | appName, 19 | components, 20 | testchecked: tests 21 | }).catch((err) => console.log(err)); 22 | } // export all component files, but don't create all application files 23 | else if (genOption === 0) { 24 | createFiles(components, path, appName, false); 25 | } 26 | }; 27 | 28 | export default exportProject; 29 | -------------------------------------------------------------------------------- /src/Dropdown/Dropdown.jsx: -------------------------------------------------------------------------------- 1 | /* This example requires Tailwind CSS v2.0+ */ 2 | import React, { Fragment } from 'react'; 3 | import '../components/ChartComponents/chartstyles.css'; 4 | 5 | function classNames(...classes) { 6 | return classes.filter(Boolean).join(' '); 7 | } 8 | 9 | export default function Dropdown(props) { 10 | let menuArray = null; 11 | if (props.data) { 12 | menuArray = Object.keys(props.data[0]).map((ele) => { 13 | return ( 14 | 17 | ); 18 | }); 19 | } 20 | 21 | return ( 22 |
23 | 32 |
33 | 38 | 39 | 40 |
41 |
42 | ); 43 | } 44 | 45 | { 46 | /*
47 | 50 |
51 | 61 |
62 | 67 | 68 | 69 |
70 |
71 |
*/ 72 | } 73 | -------------------------------------------------------------------------------- /src/Dropdown/DropdownFunctionality.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | function Dropdown({ title, items, multiSelect = false }) { 4 | const [open, setOpen] = useState(false); 5 | const [selection, setSelection] = ([]); 6 | 7 | function handleOnclick(item) {} 8 | return ( 9 |
10 |
toggle(!open)} 15 | onClick={() => toggle(!open)}> 16 |
17 |
18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/Dropdown/Menu.Item.jsx: -------------------------------------------------------------------------------- 1 | import { Menu } from '@headlessui/react' 2 | 3 | function MyDropdown() { 4 | return ( 5 | 6 | More 7 | 8 | {/* Use the `active` render prop to conditionally style the active item. */} 9 | 10 | {({ active }) => ( 11 | 17 | Account settings 18 | 19 | )} 20 | 21 | {/* ... */} 22 | 23 | 24 | ) 25 | } -------------------------------------------------------------------------------- /src/Dropdown/MenuButton.jsx: -------------------------------------------------------------------------------- 1 | import { Menu } from '@headlessui/react' 2 | 3 | function MyDropdown() { 4 | return ( 5 | 6 | More 7 | 8 | 9 | {({ active }) => ( 10 | 14 | Account settings 15 | 16 | )} 17 | 18 | 19 | {({ active }) => ( 20 | 24 | Documentation 25 | 26 | )} 27 | 28 | 29 | Invite a friend (coming soon!) 30 | 31 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/app/dictionary.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-alert, no-console */ 2 | 3 | export const dictionary = { 4 | // Dictionary is an object of objects that we use to initialize all of our charts info to generate our routes and pass down necessary props for selected containers 5 | // no reducers here since this will remain a static piece of state, unless we directly modify/add to it as we contribute to the chart codebase 6 | barchart: { 7 | type: 'barchart', 8 | name: 'BarChart', 9 | children: ['Chart, Axis, Rectangle'], 10 | properties: [ 11 | 'data', 12 | 'dataString', 13 | 'xKey', 14 | 'yKey', 15 | 'xAxisLabel', 16 | 'yAxisLabel', 17 | 'height', 18 | 'width' 19 | ] 20 | }, 21 | histogram: { 22 | type: 'histogram', 23 | name: 'Histogram', 24 | children: ['Chart, Axis, Bars'], 25 | properties: [ 26 | 'data', 27 | 'dataString', 28 | 'xKey', 29 | 'xAxisLabel', 30 | 'yAxisLabel', 31 | 'height', 32 | 'width', 33 | 'thresholds', 34 | 'barPadding' 35 | ] 36 | }, 37 | 38 | scatterplot: { 39 | type: 'scatterplot', 40 | name: 'ScatterPlot', 41 | children: ['Chart, Axis, Circles'], 42 | properties: [ 43 | 'data', 44 | 'dataString', 45 | 'xKey', 46 | 'yKey', 47 | 'xAxisLabel', 48 | 'yAxisLabel', 49 | 'height', 50 | 'width', 51 | 'radius' 52 | ] 53 | }, 54 | 55 | piechart: { 56 | type: 'piechart', 57 | name: 'PieChart', 58 | children: ['Pie'], 59 | properties: ['data', 'dataString', 'innerRadius', 'outerRadius', 'label', 'pieValue'] 60 | }, 61 | 62 | linechart: { 63 | type: 'linechart', 64 | name: 'LineChart', 65 | children: ['Chart, Axis, Line'], 66 | properties: [ 67 | 'data', 68 | 'dataString', 69 | 'xKey', 70 | 'yKey', 71 | 'xAxisLabel', 72 | 'yAxisLabel', 73 | 'height', 74 | 'width' 75 | ] 76 | } 77 | }; 78 | 79 | export default dictionary; 80 | -------------------------------------------------------------------------------- /src/app/preloadedState.js: -------------------------------------------------------------------------------- 1 | export const preloadedState = { 2 | type: '', //barchart, scatterplot, etc. 3 | name: '', 4 | children: [], // what child elements/comps are needed to build chart (idk if we need this), string[] 5 | properties: [], 6 | dictionary: { 7 | // Dictionary is an object of objects that we use to initialize all of our charts info to generate our routes and pass down necessary props for selected containers 8 | // no reducers here since this will remain a static piece of state, unless we directly modify/add to it as we contribute to the chart codebase 9 | barchart: { 10 | type: 'barchart', 11 | name: 'BarChart', 12 | children: ['Chart, Axis, Rectangle'], 13 | properties: [ 14 | 'data', 15 | 'xKey', 16 | 'yKey', 17 | 'xAxisLabel', 18 | 'yAxisLabel', 19 | 'height', 20 | 'width' 21 | ] 22 | }, 23 | histogram: { 24 | type: 'histogram', 25 | name: 'Histogram', 26 | children: ['Chart, Axis, Bars'], 27 | properties: [ 28 | 'data', 29 | 'xKey', 30 | 'xAxisLabel', 31 | 'yAxisLabel', 32 | 'height', 33 | 'width', 34 | 'thresholds', 35 | 'barPadding' 36 | ] 37 | }, 38 | 39 | scatterplot: { 40 | type: 'scatterplot', 41 | name: 'ScatterPlot', 42 | children: ['Chart, Axis, Circles'], 43 | properties: [ 44 | 'data', 45 | 'xKey', 46 | 'yKey', 47 | 'xAxisLabel', 48 | 'yAxisLabel', 49 | 'height', 50 | 'width', 51 | 'radius' 52 | ] 53 | }, 54 | 55 | piechart: { 56 | type: 'piechart', 57 | name: 'PieChart', 58 | children: ['Pie'], 59 | properties: ['data', 'innerRadius', 'outerRadius', 'label', 'pieValue'] 60 | }, 61 | 62 | linechart: { 63 | type: 'linechart', 64 | name: 'LineChart', 65 | children: ['Chart, Axis, Line'], 66 | properties: [ 67 | 'data', 68 | 'xKey', 69 | 'yKey', 70 | 'xAxisLabel', 71 | 'yAxisLabel', 72 | 'height', 73 | 'width' 74 | ] 75 | } 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /src/app/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import chartsReducer from '../features/chart/chartsSlice'; 3 | import propsReducer from '../features/chart/propsSlice'; 4 | // import preloadedState from './preloadedState'; 5 | 6 | //'reducer' acts as a root reducer here 7 | 8 | export const store = configureStore({ 9 | reducer: { 10 | charts: chartsReducer, 11 | props: propsReducer 12 | } 13 | // preloadedState: preloadedState 14 | }); 15 | -------------------------------------------------------------------------------- /src/babel-plugin-macros.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'fontawesome-svg-core': { 3 | 'license': 'free' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | return { 3 | plugins: ['macros'], 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/ChartComponents/Chart.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .Chart { 6 | overflow: visible; 7 | } 8 | 9 | .Chart text { 10 | fill: #595c5c; 11 | } 12 | 13 | .Line { 14 | transition: all 0.3s ease-out; 15 | } 16 | 17 | .Line--type-line { 18 | fill: none; 19 | stroke: #06b6d4; 20 | stroke-width: 3px; 21 | stroke-linecap: round; 22 | } 23 | 24 | .Line--type-area { 25 | fill: none; 26 | stroke-width: 0; 27 | } 28 | 29 | .Line--type-area { 30 | fill: #00dfeb; 31 | stroke-width: 0; 32 | } 33 | 34 | .Axis__line { 35 | stroke: #595c5c; 36 | } 37 | 38 | .Axis__label { 39 | text-anchor: middle; 40 | font-size: 0.9em; 41 | letter-spacing: 0.01em; 42 | } 43 | 44 | .Axis__tick { 45 | font-size: 0.8em; 46 | transition: all 0.3s ease-out; 47 | } 48 | 49 | .AxisHorizontal .Axis__tick { 50 | text-anchor: middle; 51 | } 52 | 53 | .AxisVertical .Axis__tick { 54 | dominant-baseline: middle; 55 | text-anchor: end; 56 | } 57 | 58 | .Circles__circle { 59 | fill: #00dfeb; 60 | transition: all 0.3s ease-out; 61 | } 62 | 63 | .Bars__rect { 64 | fill: #00dfeb; 65 | transition: all 0.3s ease-out; 66 | } 67 | 68 | .domain, 69 | .tick line { 70 | stroke: #dadada; 71 | } -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Arc.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Arc = ({ 4 | data, 5 | fill, 6 | stroke, 7 | strokeWidth, 8 | d, 9 | }) => { 10 | return ( 11 | 18 | ) 19 | } 20 | 21 | export default Arc -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Axis.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import * as d3 from 'd3' 3 | import { useChartDimensions } from "./Chart.jsx"; 4 | 5 | const axisComponentsByDimension = { 6 | x: AxisHorizontal, 7 | y: AxisVertical, 8 | xB: AxisBand, 9 | } 10 | const Axis = ({ dimension, ...props }) => { 11 | const dimensions = useChartDimensions() 12 | const Component = axisComponentsByDimension[dimension] 13 | if (!Component) return null 14 | 15 | return ( 16 | 20 | ) 21 | } 22 | 23 | export default Axis 24 | 25 | 26 | function AxisHorizontal({ dimensions, label, formatTick, scale, data, ...props }) { 27 | const numberOfTicks = dimensions.boundedWidth < 600 28 | ? dimensions.boundedWidth / 100 29 | : dimensions.boundedWidth / 250 30 | 31 | const ticks = scale.ticks(numberOfTicks) 32 | 33 | return ( 34 | 35 | 39 | 40 | {ticks.map((tick, i) => ( 41 | 46 | {formatTick(tick)} 47 | 48 | ))} 49 | 50 | {label && ( 51 | 55 | {label} 56 | 57 | )} 58 | 59 | ) 60 | } 61 | 62 | function AxisVertical({ dimensions, label, formatTick, scale, ...props }) { 63 | const numberOfTicks = dimensions.boundedHeight / 70 64 | 65 | const ticks = scale.ticks(numberOfTicks) 66 | 67 | return ( 68 | 69 | 73 | 74 | {ticks.map((tick, i) => ( 75 | 80 | {formatTick(tick)} 81 | 82 | ))} 83 | 84 | {label && ( 85 | 91 | {label} 92 | 93 | )} 94 | 95 | ) 96 | } 97 | 98 | function AxisBand({ dimensions, label, formatTick, scale, data, ...props }) { 99 | const numberOfTicks = dimensions.boundedWidth < 600 100 | ? dimensions.boundedWidth / 100 101 | : dimensions.boundedWidth / 250 102 | 103 | const ticks = scale.domain(); 104 | 105 | return ( 106 | 107 | 111 | 112 | {ticks.map((tick, i) => ( 113 | 118 | {(tick)} 119 | 120 | ))} 121 | 122 | {label && ( 123 | 127 | {label} 128 | 129 | )} 130 | 131 | ) 132 | } -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Bars.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import * as d3 from 'd3' 3 | import { useAccessor } from "../../../utils/utils.js"; 4 | 5 | const Bars = ({ data, keyAccessor, xAccessor, yAccessor, widthAccessor, heightAccessor, ...props }) => ( 6 | 7 | {data.map((d, i) => ( 8 | 16 | ))} 17 | 18 | ) 19 | 20 | 21 | export default Bars 22 | 23 | -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Chart.css: -------------------------------------------------------------------------------- 1 | .Chart { 2 | overflow: visible; 3 | } 4 | 5 | .Chart text { 6 | fill: #4e4f50; 7 | } 8 | 9 | .Line { 10 | transition: all 0.3s ease-out; 11 | } 12 | 13 | .Line--type-line { 14 | fill: none; 15 | stroke: #00dfeb; 16 | stroke-width: 3px; 17 | stroke-linecap: round; 18 | } 19 | 20 | .Line--type-area { 21 | fill: rgba(71, 70, 73, 0.185); 22 | stroke-width: 0; 23 | } 24 | 25 | .Axis__line { 26 | stroke: #bdc3c7; 27 | } 28 | 29 | .Axis__label { 30 | text-anchor: middle; 31 | font-size: 0.9em; 32 | letter-spacing: 0.01em; 33 | } 34 | 35 | .Axis__tick { 36 | font-size: 0.8em; 37 | transition: all 0.3s ease-out; 38 | } 39 | 40 | .AxisHorizontal .Axis__tick { 41 | text-anchor: middle; 42 | } 43 | 44 | .AxisVertical .Axis__tick { 45 | dominant-baseline: middle; 46 | text-anchor: end; 47 | } 48 | 49 | .Circles__circle { 50 | fill: #00dfeb; 51 | transition: all 0.3s ease-out; 52 | } 53 | 54 | .Bars__rect { 55 | fill: #00dfeb; 56 | transition: all 0.3s ease-out; 57 | } 58 | 59 | .domain, 60 | .tick line { 61 | stroke: #dadada; 62 | } 63 | -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Chart.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext } from "react" 2 | 3 | import "../Chart.css" 4 | 5 | const ChartContext = createContext() 6 | export const useChartDimensions = () => useContext(ChartContext) 7 | 8 | const Chart = ({ dimensions, children }) => ( 9 | 10 | 11 | 12 | {children} 13 | 14 | 15 | 16 | ) 17 | 18 | export default Chart 19 | -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/ChartWithDimensions.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @pr 3 | * @name: ChartWithDimensions 4 | * @description: produces a modularized template for all Charts 5 | * @param: (object with keys domain: number[] and range: number[]), pixelsPerTick: number 6 | * @returns: A rendering of the Chart 7 | * @author: Antonio Ayala, Sophia Chiao 8 | */ 9 | 10 | // import modules and libraries 11 | import { useChartDimensions } from '../../../ChartComponents/utilities/utils'; 12 | 13 | 14 | const chartSettings = { 15 | "marginLeft": 75 16 | } 17 | 18 | export const ChartWithDimensions = () => { 19 | 20 | // creating a custom hook useChartDimensions 21 | const [ref, dms] = useChartDimensions(chartSettings) 22 | 23 | // 24 | const xScale = useMemo(() => ( 25 | d3.scaleLinear() 26 | .domain([0, 100]) 27 | .range([0, dms.boundedWidth]) 28 | ), [dms.boundedWidth]) 29 | 30 | return ( 31 |
35 | 36 | 40 | 45 | 49 | 53 | 54 | 55 | 56 |
57 | ) 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Circles.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { accessorPropsType } from "../../../utils/utils.js" 3 | 4 | const Circles = ({ data, keyAccessor, xAccessor, yAccessor, radius }) => { 5 | 6 | return ( 7 | 8 | {data.map((d, i) => ( 9 | 16 | ))} 17 | 18 | ) 19 | } 20 | 21 | export default Circles -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/CodeRender.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback, Fragment } from 'react'; 2 | import { upperFirst } from 'lodash'; 3 | import { 4 | generateChartCode, 5 | CodeBlock, 6 | Code, 7 | CodeText, 8 | formatCode 9 | } from '../../../utils/CodePreview'; 10 | 11 | const CodeRender = ({ name, children, data, ...currProps }) => { 12 | delete currProps.dataString; // otherwise the entire dataset will be printed to the screen 13 | 14 | const code = generateChartCode(`${upperFirst(name)}`, currProps, { 15 | dataKey: data !== undefined ? 'data' : undefined, 16 | children: children, 17 | defaults: {}, 18 | pkg: name, // pkg: 'barchart', 19 | }) 20 | 21 | // References created by useRef itself do not trigger component rerenders, and on initial render, ref will be null 22 | // State must be modified to trigger any rerenders, so we use a callback ref to run some code 23 | // whenever React attaches or detaches a ref to a DOM node (html: ) 24 | const useCodeRef = (processNode) => { 25 | const [node, setNode] = useState(null); 26 | const setCodeRef = useCallback(newNode => { 27 | if (newNode) { 28 | // console.log("ref", node); // node = codeRef.current // 29 | setNode(processNode(newNode)); 30 | } 31 | }, []); 32 | return [node, setCodeRef] 33 | }; 34 | const [codeRef, setCodeRef] = useCodeRef((node) => node) 35 | 36 | // To reflect on every code change, we use useEffect to reassign the new codeRef on rerender 37 | useEffect(() => { 38 | console.log(codeRef); 39 | }, [codeRef]); 40 | 41 | return ( 42 | 43 |
44 | 45 | 46 | {code} 47 | 48 | 49 |
50 |
51 | 107 |
108 |
109 | ); 110 | }; 111 | 112 | export default CodeRender; 113 | -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Container.jsx: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense, useEffect, useMemo, useLayoutEffect, useCallback, Fragment, useState } from 'react'; 2 | import * as d3 from 'd3'; 3 | import Form from './Form.jsx' 4 | import CodeRender from './CodeRender.jsx' 5 | import { useSelector, useDispatch } from 'react-redux' 6 | import { useLocation } from "react-router"; 7 | import { Link } from 'react-router-dom'; 8 | import '../chartstyles.css' 9 | // Remember to import your specific chart from the chartsSlice here 10 | import { 11 | barchart, 12 | histogram, 13 | scatterplot, 14 | piechart, 15 | linechart, 16 | } from "../../../features/chart/chartsSlice" 17 | import ErrorBoundary from './ErrorBoundary.jsx'; 18 | 19 | const BarChart = lazy(() => import('../../Charts/BarChart/JSX/BarChart.jsx')); 20 | const Histogram = lazy(() => import('../../Charts/Histogram/JSX/Histogram.jsx')) 21 | const ScatterPlot = lazy(() => import('../../Charts/ScatterPlot/JSX/ScatterPlot.jsx')) 22 | const PieChart = lazy(() => import('../../Charts/PieChart/JSX/PieChart.jsx')) 23 | const LineChart = lazy(() => import('../../Charts/LineChart/JSX/LineChart.jsx')) 24 | 25 | // Upon navigation to specified route, we first identify our chart using useLocation, then, we will dispatch action using specified chart 26 | // Can we do this with useParams as well? --> grab name direclty without slicing and reassigning 27 | 28 | /** 29 | * 30 | * @param { type, name, children, properties } Container 31 | * @returns Form, MyChart, CodeRender 32 | * 33 | * Container is the general component which contains all of our other modularized components. 34 | * On route selection, we use property accesors for our dispatch (chart selection), to programmatically set the chart-unique type, props, children, etc. in state 35 | * This selection allows us to: 36 | * - dynamically lazy() load in the required chart file when needed 37 | * - filter out chart-specific props to pass/populate the modular child components 38 | * 39 | */ 40 | 41 | 42 | const Container = ({ type, name, children, properties }) => { 43 | // use property accessors for our dispatch 44 | const charts = { 45 | "barchart": barchart, 46 | "histogram": histogram, 47 | "scatterplot": scatterplot, 48 | "piechart": piechart, 49 | "linechart": linechart, 50 | } 51 | 52 | //Dispatching chart sets the chart type, props, children, and default props in state 53 | const dispatch = useDispatch(); 54 | useEffect(() => { 55 | console.log("dispatching chart action type: ") 56 | dispatch(charts[type]()); 57 | }, [dispatch]); 58 | 59 | //Filtering chart-specific props 60 | const props = useSelector((state) => state.props); // object of all current props 61 | const currProps = properties.reduce((acc, curr) => { 62 | acc[curr] = props[curr]; 63 | return acc; 64 | }, {}); 65 | 66 | // Need to implement a unique key that increases every time state changes. 67 | // This is then passed to error boundary so that it rerenders when state chagnes. 68 | // This is necessary in order to recover from errors. Without Changing the error boundaries key won't reset. 69 | const [errorKey, setErrorKey] = useState(0); 70 | useEffect(() => { 71 | setErrorKey(Date.now()); 72 | }, [currProps.data]) 73 | 74 | // Memoizing the import 75 | // We want to rerender of chart as state props changes, but import the actual component only once (unless type change during dispatch) 76 | const MyChart = useMemo(() => lazy(() => import(`../../Charts/${name}/JSX/${name}.jsx`)), [dispatch]); 77 | 78 | return ( 79 | 80 | {/* }> */} 81 |
Home
82 | 83 |
84 | 85 |
86 |
91 |
92 | 93 |
94 | 95 | }> 96 | 99 | 100 | 101 |
102 | 103 | 109 | 110 |
111 | 112 |
113 | 114 |
115 | {/*
*/} 116 |
117 | ); 118 | }; 119 | 120 | export default Container; 121 | -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/ErrorBoundary.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class ErrorBoundary extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { hasError: false }; 7 | } 8 | 9 | static getDerivedStateFromError(error) { 10 | // Update state so the next render will show the fallback UI. 11 | return { hasError: true }; 12 | } 13 | 14 | componentDidCatch(error, errorInfo) { 15 | console.log(error); 16 | } 17 | 18 | render() { 19 | if (this.state.hasError) { 20 | // You can render any custom fallback UI 21 | return
22 | } 23 | 24 | return this.props.children; 25 | } 26 | } 27 | 28 | export default ErrorBoundary; -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/ExportCodeButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { formatCode } from '../../../utils/CodePreview'; 3 | import { downloadCode } from "../../../utils/ExportData" 4 | 5 | export const ExportCodeButton = ({ name, codeRef }) => { 6 | return ( 7 | 25 | ); 26 | }; 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/ExportDataButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { upperFirst } from 'lodash'; 3 | 4 | //Exports data as JS file 5 | export const ExportDataButton = ({ name, data }) => { 6 | 7 | let fileName = upperFirst(name); 8 | let text = `export const data = [${data 9 | .reduce((str, obj) => { 10 | return (str += 11 | '{' + Object.keys(obj).map((key) => `'${key}': ${obj[key]}`) + `}, \n`); 12 | }, '') 13 | .trim() 14 | .slice(0, -1)}]`; 15 | 16 | return ( 17 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Form.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | import '../../ChartComponents/chartstyles.css'; 3 | import { startCase } from 'lodash'; 4 | import { useDispatch } from 'react-redux'; 5 | import { changeProps } from '../../../features/chart/propsSlice'; 6 | import Dropdown from '../../../Dropdown/Dropdown'; 7 | 8 | /** 9 | * We do not dynamically use eval() and instead use property accessors to send the correct payload instead 10 | * Instead, we let our reducer handle what gets updated 11 | * We pass input name/value to our single props reducer, which updates the props in state.props 12 | * 13 | * Depending on the type of prop, we render specific form elements (text input, dropdown, slider) 14 | */ 15 | 16 | const Form = ({ properties, data, currProps }) => { 17 | const dispatch = useDispatch(); 18 | 19 | const handleChange = useCallback( 20 | (e) => { 21 | e.preventDefault(); 22 | console.log('handling event...', e); 23 | dispatch(changeProps({ name: e.target.name, value: e.target.value })); 24 | }, 25 | [dispatch] 26 | ); 27 | 28 | const [invalidJSON, setInvalidJSON] = useState(false); 29 | 30 | const inputs = properties.map((p, i) => { 31 | //If Property is xKey or yKey then do a form 32 | if (p === 'xKey' || p === 'yKey') { 33 | console.log('has Data Changed to undefined?', data) 34 | return ( 35 |
36 |
37 | 41 | 42 | 48 |
49 |
50 | ); 51 | } else if (typeof currProps[p] === 'number') { 52 | return ( 53 |
54 |
55 | 60 | 71 |
72 |
73 | ); 74 | } else if (p === 'data') { 75 | //Data needs to be a current property, but doesn't require it's own form field. 76 | return null 77 | } 78 | 79 | else if (p === 'dataString') { 80 | //Otherwise do a TextArea 81 | return ( 82 |
83 |
84 | 88 | 106 | {invalidJSON ?

Invalid JSON

: ''} 107 |
108 |
109 | ); 110 | } else { 111 | //Otherwise do a TextArea 112 | return ( 113 |
114 |
115 | 119 | 127 |
128 |
129 | ); 130 | } 131 | }); 132 | 133 | return ( 134 | { }}> 135 |
{inputs}
136 | 137 | ); 138 | }; 139 | 140 | export default Form; 141 | -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Gradient.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | const Gradient = ({ id, colors, ...props }) => ( 4 | 5 | {colors.map((color, i) => ( 6 | 11 | ))} 12 | 13 | ) 14 | 15 | export default Gradient -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Line.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import * as d3 from "d3" 3 | 4 | const Line = ({ data, xAccessor, yAccessor, y0Accessor, width, height, /* type, interpolation, ...props */ }) => { 5 | const lineGenerator = d3.line() 6 | .x(xAccessor) 7 | // .y0(d=> Math.min(0, yscale(yAccessor))) 8 | .y(yAccessor) 9 | .curve(d3.curveMonotoneX) 10 | 11 | return ( 12 | 17 | ) 18 | } 19 | 20 | 21 | export default Line -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Pie.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import styled from 'styled-components'; 3 | import * as d3 from 'd3'; 4 | import { pie } from 'd3'; 5 | import { sum } from 'lodash'; 6 | import Arc from './Arc'; 7 | 8 | 9 | const Pie = ({ 10 | data, 11 | width, 12 | height, 13 | innerRadius, 14 | outerRadius, 15 | label, 16 | pieValue 17 | 18 | }) => { 19 | // d3.select('#pie-container') 20 | // .select('svg') 21 | // .remove(); 22 | 23 | const colorScale = d3 24 | .scaleSequential() 25 | .interpolator(d3.interpolateHcl("#60c96e", "#4d4193")) 26 | .domain([0, data.length]); 27 | 28 | // // Create new svg --> Chart 29 | // const svg = d3 30 | // .select('#pie-container') 31 | // .append('svg') 32 | // .attr('width', width) 33 | // .attr('height', height) 34 | // .append('g') 35 | // .attr('transform', `translate(${width / 2}, ${height / 2})`); 36 | 37 | const arcGenerator = d3 38 | .arc() 39 | .innerRadius(innerRadius) 40 | .outerRadius(outerRadius); 41 | 42 | const pieGenerator = d3 43 | .pie() 44 | .padAngle(0) 45 | .value((d) => d[pieValue]); 46 | 47 | const pie = pieGenerator(data); 48 | 49 | // // const propsPie = useMemo(() => pie.map((d) => ({ [label]: d.data[label], [pieValue]: d.data[pieValue] })), [data]); 50 | const propsPie = pie.map((d) => ({ [label]: d.data[label], [pieValue]: d.data[pieValue] })); 51 | 52 | // for text label 53 | const translatePie = (d) => { 54 | const [x, y] = arcGenerator.centroid(d); 55 | return `translate(${x}, ${y})`; 56 | }; 57 | 58 | const PieLabel = styled.text` 59 | font-size: 10; 60 | text-anchor: middle; 61 | alignment-baseline: middle; 62 | fill: black; 63 | `; 64 | 65 | // const arc = svg 66 | // .selectAll() 67 | // .data(pieGenerator(data)) 68 | // .enter(); 69 | 70 | // // Append arcs --> Arc.jsx 71 | // arc 72 | // .append('path') 73 | // .attr('d', arcGenerator) 74 | // .style('fill', (_, i) => colorScale(i)) 75 | // .style('stroke', '#ffffff') 76 | // .style('stroke-width', 0); 77 | 78 | // // Append text labels --> pieLabel 79 | // arc 80 | // .append('text') 81 | // .attr('text-anchor', 'middle') 82 | // .attr('alignment-baseline', 'middle') 83 | // .text((d) => { 84 | // if (!d.data[label]) return `` 85 | // if (d.data[label]) return `${d.data[label]} ${d.data[pieValue]}` 86 | // }) 87 | // .style('fill', 'black') 88 | // .style('font-size', 10) 89 | // .attr('transform', (d) => { 90 | // const [x, y] = arcGenerator.centroid(d); 91 | // return `translate(${x}, ${y})`; 92 | // }); 93 | 94 | return ( 95 | //
96 | 97 | {pie.map((d, i) => ( 98 | 99 | 108 | {d.data[label] && ( 109 | 112 | {d.data[label]} {d.data[pieValue]} 113 | 114 | ) 115 | } 116 | 117 | ))} 118 | 119 | ) 120 | } 121 | export default Pie -------------------------------------------------------------------------------- /src/components/ChartComponents/JSX/Rectangle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Bar = styled.rect` 5 | fill-opacity: 0.7; 6 | color: palevioletred; 7 | `; 8 | 9 | const Rectangle = ({ 10 | data, 11 | x, 12 | y, 13 | width, 14 | height, 15 | fill 16 | }) => { 17 | 18 | return ( 19 | 26 | ); 27 | }; 28 | 29 | export default Rectangle -------------------------------------------------------------------------------- /src/components/ChartComponents/chartstyles.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* body { 6 | padding: 1.6em 2em 4em; 7 | letter-spacing: -0.011em; 8 | font-family: 'Inter var', sans-serif; 9 | font-size: 16px; 10 | color: #34495e; 11 | background: #f1f3f5; 12 | } */ 13 | 14 | #root, 15 | .App { 16 | width: 100%; 17 | font-family: 'Inter var', sans-serif; 18 | } 19 | 20 | h1 { 21 | font-weight: 900; 22 | margin: 0.4em 0 0.6em; 23 | } 24 | 25 | /* placeholders */ 26 | .placeholder { 27 | background: #ecf0f1; 28 | } 29 | 30 | .Timeline { 31 | height: 300px; 32 | min-width: 500px; 33 | width: calc(100% + 1em); 34 | margin-bottom: 2em; 35 | } 36 | 37 | .Histogram { 38 | height: 500px; 39 | flex: 1; 40 | min-width: 500px; 41 | overflow: hidden; 42 | } 43 | 44 | .BarChart { 45 | height: 500px; 46 | flex: 1; 47 | min-width: 500px; 48 | overflow: hidden; 49 | } 50 | 51 | .ScatterPlot { 52 | height: 500px; 53 | width: 500px; 54 | margin-right: 2em; 55 | } 56 | 57 | /* .LineGraph { 58 | height: 500px; 59 | width: 500px; 60 | margin-right: 2em; 61 | } */ 62 | 63 | .App__charts { 64 | display: flex; 65 | align-items: center; 66 | flex-wrap: wrap; 67 | margin: -0.5em; 68 | } 69 | 70 | .Timeline, 71 | .ScatterPlot, 72 | .Histogram, 73 | /* .LineGraph, */ 74 | .BarChart { 75 | /* background: rgb(255,255,255); */ 76 | } 77 | -------------------------------------------------------------------------------- /src/components/Charts/BarChart/JSX/BarChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as d3 from 'd3'; 3 | import { useChartDimensions } from '../../../../utils/utils.js'; 4 | import Axis from '../../../ChartComponents/JSX/Axis.jsx'; 5 | import Rectangle from '../../../ChartComponents/JSX/Rectangle.jsx'; 6 | import Chart from '../../../ChartComponents/JSX/Chart.jsx'; 7 | import '../../../ChartComponents/chartstyles.css'; 8 | import '../../../ChartComponents/Chart.css'; 9 | import { useUniqueId } from '../../../../utils/utils.js'; 10 | import Gradient from "../../../ChartComponents/JSX/Gradient" 11 | 12 | /** 13 | * Because of the way the user will import data in their customized code 14 | * ex. 15 | * the base component template has to be able to take props supplied from MyBarChart.jsx 16 | */ 17 | 18 | export default function BarChart ({ data, xKey, yKey, xAxisLabel, yAxisLabel, height, width }) { 19 | // export default function BarChart ({ currProps: { data, xKey, yKey, xAxisLabel, yAxisLabel, height, width }}) { 20 | 21 | // if(!data) data = []; 22 | /* 23 | Using useMemo for **referential equality** of depedencies: important for React hooks 24 | 2 common use cases of useMemo: 25 | 1. When you want to make a slow function wrap inside useMemo so that doesn't re-compute every single time you render your component and it only computed when you acually need the value from that function since the inputs actually change 26 | 2. Whenever you want to make sure the reference of an object or an array is exactly the same as it was the last time you rendered if none of the internal workings changed, you're gonna want to useMemo here to make sure that you only update the reference of that object whenever the actual contents of the object change instead of updating every single time you render 27 | */ 28 | 29 | // Uncomment below to work with current histogram data (working) 30 | // (oh also useMemo doesn't work so i will get to that later :( ) 31 | // const xAccessor = useMemo(() => (data) => data[xKey], []); 32 | // const yAccessor = useMemo(() => (data) => data[yKey], []); 33 | const xAccessor = (data) => data[xKey]; 34 | const yAccessor = (data) => data[yKey]; 35 | 36 | const gradientId = useUniqueId("Histogram-gradient") 37 | const gradientColors = ["#9980FA", "rgb(226, 222, 243)"] 38 | 39 | // setState input dimensions from Form -> Container passes down updated dims -> Chart passes dims as new args in useChartDimensions 40 | const [ref, dimensions] = useChartDimensions({ 41 | marginBottom: 77, 42 | height: height, 43 | width: width 44 | }); 45 | 46 | /** For bar charts, it's recommended to use scaleBand() + continuous.rangeRound() fn (since d3 v4) to set the range of the scale to the specified array of values, but in our case, I think either works */ 47 | // Should we add a form input to control the padding ? 48 | // Outer padding: space before the first bar and after the last one. 49 | // Inner padding: space between bars 50 | const xScale = d3 51 | .scaleBand() 52 | .domain(data.map(xAccessor)) 53 | .paddingInner(0.1) 54 | .paddingOuter(0.1) 55 | .rangeRound([0, dimensions.boundedWidth]) 56 | // .padding(0.1) 57 | // .range([0, dimensions.boundedWidth]); 58 | 59 | // Add a form input for user input to change y-scale min, instead of default to 0? 60 | let yMax = d3.max(data, yAccessor); 61 | let yMin = Math.min(0, d3.min(data, yAccessor)); 62 | const yScale = d3 63 | .scaleLinear() 64 | // .domain(d3.extent(data, yAccessor)) 65 | .domain([yMin, yMax]) 66 | .range([dimensions.boundedHeight, 0]) 67 | .nice(); 68 | 69 | const Bars = data.map((d, i) => { 70 | return ( 71 | 80 | ); 81 | }); 82 | 83 | 84 | 85 | return ( 86 |
87 | 88 | 89 | 95 | 96 | 102 | 108 | {Bars} 109 | 110 | 111 | 112 |
113 | ); 114 | }; 115 | 116 | // export default BarChart; 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/components/Charts/Histogram/JSX/Histogram.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, Fragment } from 'react'; 2 | import * as d3 from 'd3'; 3 | import { useChartDimensions } from '../../../../utils/utils.js'; 4 | import Axis from '../../../ChartComponents/JSX/Axis.jsx'; 5 | import Bars from '../../../ChartComponents/JSX/Bars.jsx'; 6 | import Chart from '../../../ChartComponents/JSX/Chart.jsx'; 7 | import '../../../ChartComponents/chartstyles.css'; 8 | import '../../../ChartComponents/Chart.css'; 9 | import { useUniqueId } from '../../../../utils/utils.js'; 10 | import Gradient from "../../../ChartComponents/JSX/Gradient" 11 | 12 | const Histogram = ({ data, xKey, xAxisLabel, yAxisLabel, height, width, thresholds, barPadding }) => { 13 | // Since histograms compare occurences across a population/data, the y-Accessor must be the length of your dataset 14 | // const yAccessor = d => d.length 15 | const xAccessor = useMemo(() => (data) => data[xKey]); 16 | const yAccessor = useMemo(() => (data) => data.length); 17 | 18 | const gradientId = useUniqueId("Histogram-gradient") 19 | const gradientColors = ["#9980FA", "rgb(226, 222, 243)"] 20 | 21 | // setState input dimensions from Form -> Container passes down updated dims -> Chart passes dims as new args in useChartDimensions 22 | const [ref, dimensions] = useChartDimensions({ 23 | marginBottom: 77, 24 | height: height, 25 | width: width 26 | }); 27 | 28 | // Thresholds = # scaled bins (user inputs # of bins as thresholds, we scale bins according to their data for them ) 29 | const numberOfThresholds = thresholds; 30 | 31 | const xScale = d3 32 | .scaleLinear() 33 | .domain(d3.extent(data, xAccessor)) 34 | .range([0, dimensions.boundedWidth]) 35 | .nice(numberOfThresholds); 36 | 37 | const binsGenerator = d3 38 | .histogram() 39 | .domain(xScale.domain()) 40 | .value(xAccessor) 41 | .thresholds(xScale.ticks(numberOfThresholds)); 42 | 43 | const bins = binsGenerator(data); 44 | 45 | const yScale = d3 46 | .scaleLinear() 47 | .domain([0, d3.max(bins, yAccessor)]) 48 | .range([dimensions.boundedHeight, 0]) 49 | .nice(); 50 | 51 | const xAccessorScaled = (d) => xScale(d.x0) + barPadding; 52 | const yAccessorScaled = (d) => yScale(yAccessor(d)); 53 | const widthAccessorScaled = (d) => xScale(d.x1) - xScale(d.x0) - barPadding; 54 | const heightAccessorScaled = (d) => 55 | dimensions.boundedHeight - yScale(yAccessor(d)); 56 | const keyAccessor = (d, i) => i; 57 | 58 | return ( 59 | 60 |
61 | 62 | 63 | 69 | 70 | 76 | 82 | 91 | 92 |
93 |
94 | ); 95 | }; 96 | 97 | export default Histogram; 98 | -------------------------------------------------------------------------------- /src/components/Charts/LineChart/JSX/LineChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, Fragment } from 'react'; 2 | import * as d3 from 'd3'; 3 | import { useChartDimensions } from '../../../../utils/utils.js'; 4 | import Chart from '../../../ChartComponents/JSX/Chart.jsx'; 5 | import Line from '../../../ChartComponents/JSX/Line.jsx'; 6 | import Axis from '../../../ChartComponents/JSX/Axis.jsx'; 7 | import '../../../ChartComponents/chartstyles.css'; 8 | import '../../../ChartComponents/Chart.css'; 9 | 10 | export default function LineChart({ data, xKey, yKey, xAxisLabel, yAxisLabel, height, width }) { 11 | const xAccessor = (data) => data[xKey]; 12 | const yAccessor = (data) => data[yKey]; 13 | 14 | const [ref, dimensions] = useChartDimensions({ 15 | marginBottom: 77, 16 | height: height, 17 | width: width, 18 | }) 19 | 20 | const xScale = d3 21 | .scaleLinear() // returns position within domain and range 22 | .domain(d3.extent(data, xAccessor)) // sets domain with an array [0.2693916329035372, 0.7248443066197088] 23 | .range([0, dimensions.boundedWidth]) 24 | .nice(); 25 | 26 | const yScale = d3.scaleLinear() 27 | .domain(d3.extent(data, yAccessor)) 28 | .range([dimensions.boundedHeight, 0]) 29 | .nice() 30 | 31 | const xAccessorScaled = d => xScale(xAccessor(d)) 32 | const yAccessorScaled = d => yScale(yAccessor(d)) 33 | const y0AccessorScaled = yScale(yScale.domain()[0]) 34 | 35 | return ( 36 | 37 |
38 | 39 | 45 | 51 | 59 | 60 |
61 |
62 | ) 63 | } -------------------------------------------------------------------------------- /src/components/Charts/PieChart/JSX/PieChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, Fragment } from 'react'; 2 | import * as d3 from 'd3'; 3 | import { useChartDimensions } from '../../../../utils/utils.js'; 4 | import Pie from '../../../ChartComponents/JSX/Pie.jsx'; 5 | import Chart from '../../../ChartComponents/JSX/Chart.jsx'; 6 | import '../../../ChartComponents/chartstyles.css'; 7 | import '../../../ChartComponents/Chart.css'; 8 | 9 | const PieChart = ({ data, innerRadius, outerRadius, label, pieValue }) => { 10 | 11 | // We can over-write the default values set in useChartDimensions by passing them in as props 12 | // Centering Pie 13 | const [ref, dimensions] = useChartDimensions({ 14 | marginTop: 200, 15 | marginBottom: 170, 16 | marginLeft: 270, 17 | marginRight: 170 18 | }); 19 | 20 | const width = 2 * outerRadius + dimensions.marginLeft + dimensions.marginRight; 21 | const height = 2 * outerRadius + dimensions.marginTop + dimensions.marginBottom; 22 | 23 | return ( 24 | 25 |
26 | 27 | 36 | 37 |
38 |
39 | ); 40 | } 41 | 42 | export default PieChart; -------------------------------------------------------------------------------- /src/components/Charts/ScatterPlot/JSX/ScatterPlot.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as d3 from 'd3'; 3 | import { useChartDimensions } from '../../../../utils/utils.js'; 4 | import Axis from "../../../ChartComponents/JSX/Axis.jsx" 5 | import Circles from "../../../ChartComponents/JSX/Circles.jsx" 6 | import Chart from "../../../ChartComponents/JSX/Chart.jsx" 7 | import "../../../ChartComponents/chartstyles.css" 8 | import '../../../ChartComponents/Chart.css'; 9 | 10 | const ScatterPlot = ({ data, xKey, yKey, xAxisLabel, yAxisLabel, height, width, radius }) => { 11 | const xAccessor = (data) => data[xKey]; 12 | const yAccessor = (data) => data[yKey]; 13 | 14 | const [ref, dimensions] = useChartDimensions({ 15 | marginBottom: 77, 16 | height: height, 17 | width: width, 18 | }) 19 | 20 | //Scatterplot x-range data must be numeric 21 | const xScale = d3 22 | .scaleLinear() // returns position within domain and range 23 | .domain(d3.extent(data, xAccessor)) // sets domain with an array [0.2693916329035372, 0.7248443066197088] 24 | .range([0, dimensions.boundedWidth]) 25 | .nice(); 26 | 27 | const yScale = d3.scaleLinear() 28 | .domain(d3.extent(data, yAccessor)) 29 | .range([dimensions.boundedHeight, 0]) 30 | .nice() 31 | 32 | const xAccessorScaled = d => xScale(xAccessor(d)) // returns a position from result of getting humidity in object 33 | const yAccessorScaled = d => yScale(yAccessor(d)) 34 | const keyAccessor = (d, i) => i 35 | 36 | return ( 37 |
38 | 39 | 45 | 51 | 58 | 59 |
60 | ) 61 | } 62 | 63 | export default ScatterPlot -------------------------------------------------------------------------------- /src/components/HelloWorldcopy.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const HelloWorld = () => { 4 | return ( 5 |
6 |

hello

7 |
8 | ) 9 | } -------------------------------------------------------------------------------- /src/components/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom' 3 | 4 | type props = string; 5 | 6 | export default function NavBar () { 7 | return( 8 |
9 | Bar Chart 10 | Line Chart 11 | Scatter Plot 12 | Histogram 13 |
) 14 | } -------------------------------------------------------------------------------- /src/components/pages/ChartCards.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | function ChartCards() { 5 | return ( 6 |
7 |
8 |
9 |
10 | 17 | 22 | 23 | 24 | BarChart 25 | 26 |
27 |

28 | A bar chart or bar graph plots numeric values for categorical data 29 | with rectangular bars with heights or lengths proportional to the 30 | values that they represent. 31 |

32 |
33 |
34 |
35 | 36 | 37 | Line Chart 38 | 39 |
40 |

41 | This chart is used to show information that changes over time. Line 42 | charts are created by plotting a series of several points and 43 | connecting them with a striaght line. 44 |

45 |
46 |
47 |
48 | 49 | 50 | ScatterPlot 51 | 52 |
53 |

54 | A type of data visualization that shows the relationship between 55 | different variables. This data is shown by placing various data 56 | points between an x- and y-axis. 57 |

58 |
59 |
60 |
61 | 62 | 63 | Histogram 64 | 65 |
66 |

67 | Are used to summarize discrete or continuous data that are measured 68 | on an interval scale. 69 |

70 |
71 |
72 |
73 | 74 | 75 | Pie Chart 76 | 77 |
78 |

79 | Are used to show percentages of a whole, and represents percentages 80 | at a set point in time. 81 |

82 |
83 |
84 | {/*
*/} 85 |
86 | 87 | Timeline Chart 88 |
89 |

90 | Timeline charts illustrate events, in chronological order. For example week, month, year, quarter. 91 |

92 |
93 |
94 |
95 | 96 | Bubble Graph 97 |
98 |

99 | An extension of a scatterplot, a bubble chart is commonly used to visualize relationships between three or more numeric variables.

100 |
101 | 102 |
103 |
104 | 105 | Graph Chart 106 |
107 |

108 | Graph charts show relationships between data and are intended to display the data in a way that is easy to understand and remember.

109 |
110 |
111 |
112 |
113 | ); 114 | } 115 | 116 | export default ChartCards; 117 | 118 | { 119 | /*
120 |
121 | 122 | Chart Mixed 123 |
124 |

125 | This is where we will display chart info for each graph 126 |

127 |
*/ 128 | } 129 | -------------------------------------------------------------------------------- /src/components/pages/Homepage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import ChartCards from './ChartCards' 4 | import TheCarousel from './TheCarousel'; 5 | 6 | function Homepage() { 7 | return ( 8 |
9 | 10 | 11 |
12 | ) 13 | } 14 | 15 | export default Homepage; -------------------------------------------------------------------------------- /src/components/pages/TheCarousel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ReactDOM } from 'react'; 3 | import { render } from 'react-dom'; 4 | import { Carousel } from 'react-responsive-carousel'; 5 | import styles from 'react-responsive-carousel/lib/styles/carousel.min.css'; 6 | import myStyles from '../../components/../styles.css' 7 | import chart1 from './chart1.svg'; 8 | import chart2 from './chart2.svg'; 9 | import chart3 from './chart3.svg'; 10 | import chart4 from './chart4.svg'; 11 | import chart5 from './chart5.svg'; 12 | 13 | function CarouselComponent() { 14 | return ( 15 |
16 | 24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 |
41 | ); 42 | } 43 | 44 | export default CarouselComponent; 45 | -------------------------------------------------------------------------------- /src/components/pages/chart1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.2 5 | 0.4 6 | 0.6 7 | 0.8 8 | X-axis: Humidity 9 | 10 | 11 | 12 | 13 | 0 14 | 10 15 | 20 16 | 30 17 | 40 18 | Y-axis: Temperature 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/pages/chart3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Adelie 39.2 9 | Gentoo 34.1 10 | Emperor 42 11 | Fairy 37.8 12 | Rock 37.8 13 | -------------------------------------------------------------------------------- /src/components/pages/chart4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0.2 6 | 0.3 7 | 0.4 8 | 0.5 9 | 0.6 10 | 0.7 11 | X-axis: Humidity 12 | 13 | 0 14 | 5 15 | 10 16 | 15 17 | 20 18 | Y-axis: Temperature 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/pages/chart5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0 6 | 10 7 | 203040 8 | 50 9 | X-axis: Days of Walking 10 | 11 | 12 | 0 13 | 20 14 | 40 15 | 60 16 | 80 17 | 100 18 | Y-axis: Steps 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/src/dashboard.png -------------------------------------------------------------------------------- /src/features/chart/chartsSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | type: '', //barchart, scatterplot, etc. 5 | name: '', 6 | children: [], // what child elements/comps are needed to build chart (idk if we need this), string[] 7 | properties: [] //required properties for each chart, string[], 8 | }; 9 | 10 | //make switch case based on action/chart type (action = barchart, no payload), or use switch case within the payload/reducer? (if payload = barchart) 11 | 12 | export const chartsSlice = createSlice({ 13 | name: 'charts', 14 | initialState, 15 | // reducers: An object containing Redux "case reducer" functions (functions intended to handle a specific action type, equivalent to a single case statement in a switch). 16 | // keys in the object will be used to generate string action type constants 17 | // equivalent of action type charts/barchart 18 | // under the hood: const barchart = createAction('charts/barchart') 19 | reducers: { 20 | // we are writing "mutating" reducers like we are updating state directly, 21 | // but under-the-hood the reducer receives a proxy state that translates all mutations into equivalent copy operations (possible through Immer) 22 | 23 | barchart: (state, action) => { 24 | console.log('Selecting barchart type, children, and properties'); 25 | state.type = 'barchart'; 26 | state.name = 'BarChart'; 27 | state.children = ['Chart, Axis, Rectangle']; 28 | state.properties = [ 29 | 'data', 30 | 'xKey', 31 | 'yKey', 32 | 'xAxisLabel', 33 | 'yAxisLabel', 34 | 'height', 35 | 'width' 36 | ]; 37 | }, 38 | 39 | histogram: (state, action) => { 40 | console.log('Selecting histogram type, children, and properties'); 41 | state.type = 'histogram'; 42 | state.name = 'Histogram'; 43 | state.children = ['Chart, Axis, Bars']; 44 | state.properties = [ 45 | 'data', 46 | 'xKey', 47 | 'xAxisLabel', 48 | 'yAxisLabel', 49 | 'height', 50 | 'width', 51 | 'thresholds', 52 | 'barPadding' 53 | ]; 54 | }, 55 | 56 | scatterplot: (state, action) => { 57 | console.log('Selecting scatterplot type, children, and properties'); 58 | state.type = 'scatterplot'; 59 | state.name = 'ScatterPlot'; 60 | state.children = ['Chart, Axis, Circles']; 61 | state.properties = [ 62 | 'data', 63 | 'xKey', 64 | 'yKey', 65 | 'xAxisLabel', 66 | 'yAxisLabel', 67 | 'height', 68 | 'width', 69 | 'radius' 70 | ]; 71 | }, 72 | 73 | piechart: (state, action) => { 74 | console.log('Selecting piechart type, children, and properties'); 75 | state.type = 'piechart'; 76 | state.name = 'PieChart'; 77 | state.children = ['Pie']; 78 | state.properties = [ 79 | 'data', 80 | 'innerRadius', 81 | 'outerRadius', 82 | 'label', 83 | 'pieValue' 84 | ]; 85 | }, 86 | 87 | linechart: (state, action) => { 88 | console.log('Selecting linechart type, children, and properties'); 89 | state.type = 'linechart'; 90 | state.name = 'LineChart'; 91 | state.children = ['Chart, Axis, Line']; 92 | state.properties = [ 93 | 'data', 94 | 'xKey', 95 | 'yKey', 96 | 'xAxisLabel', 97 | 'yAxisLabel', 98 | 'height', 99 | 'width' 100 | ]; 101 | } 102 | } 103 | }); 104 | 105 | // Action creators are generated for each case reducer function 106 | export const { barchart, scatterplot, histogram, piechart, linechart } = 107 | chartsSlice.actions; 108 | 109 | export default chartsSlice.reducer; 110 | -------------------------------------------------------------------------------- /src/features/chart/propsSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { sampleData } from '../../utils/dummypenguinsdata'; 3 | import { sampleFruitData } from '../../utils/dummyfruitsdata'; 4 | //this slice/reducer is the functionality of our generic handleChange 5 | 6 | /* Redux Toolkit allows us to write "mutating" logic in reducers. It 7 | doesn't actually mutate the state because it uses the Immer library, 8 | which detects changes to a "draft state" and produces a brand new 9 | immutable state based off those changes 10 | 11 | createSlice(): A function that accepts an initial state, 12 | an object of reducer functions, and a "slice name", 13 | and automatically generates action creators and action types that correspond to the reducers and state. 14 | 15 | This API is the standard approach for writing Redux logic. 16 | 17 | Internally, it uses createAction and createReducer, 18 | so you may also use Immer to write "mutating" immutable updates: 19 | 20 | action type: props/changeProps 21 | */ 22 | 23 | const initialState = { 24 | name: '', 25 | dataString: JSON.stringify(sampleData), 26 | data: sampleData, 27 | xKey: '', 28 | yKey: '', 29 | xAxisLabel: 'X-axis: Species', 30 | yAxisLabel: 'Y-axis: Body Mass', 31 | height: 500, 32 | width: 500, 33 | thresholds: 9, 34 | barPadding: 2, 35 | radius: 5, 36 | innerRadius: 0, 37 | outerRadius: 100, 38 | label: '', 39 | pieValue: '' 40 | }; 41 | 42 | export const propsSlice = createSlice({ 43 | name: 'props', 44 | initialState, 45 | reducers: { 46 | changeProps: (state, action) => { 47 | let dataVal; 48 | let numVal; 49 | const { name, value } = action.payload; 50 | // console.log(typeof name); 51 | // console.log(`Updating ${name}`); 52 | 53 | // Need to keep a seperate dataString property to handle cases where the JSON.Parse is invalid. 54 | // When the dataString changes it should also try to update the dataVal 55 | if (name === 'dataString') { 56 | 57 | try{ 58 | state['data'] = JSON.parse(value) 59 | console.log(dataVal, 'StateSet') 60 | } catch (error) { 61 | console.log(error, 'error occured') 62 | state['data'] = undefined; 63 | } 64 | } 65 | if (name === 'height' || name === 'width') 66 | numVal = +value < 100 ? 500 : parseInt(value); 67 | else if (name === 'radius') numVal = +value < 1 ? 5 : parseInt(value); 68 | else if (name === 'outerRadius') 69 | numVal = +value > 1000 ? 100 : parseInt(value); 70 | else if (name === 'innerRadius') 71 | numVal = +value > state.outerRadius ? state.outerRadius : parseInt(value); 72 | else if (name === 'barPadding' || name === 'thresholds') numVal = +value; 73 | 74 | state[name] = numVal || value; 75 | } 76 | } 77 | }); 78 | 79 | // Action creators are generated for each case reducer function 80 | export const { changeProps } = propsSlice.actions; 81 | 82 | export default propsSlice.reducer; 83 | 84 | // Initial extra boilerplate-y reducers from Approach 1, where i set up each action type and dispatch actions based on name in the form, but this is v repetitive and not dry (but now we only need the one props reducer above !) 85 | 86 | // changeName: (state, action) => { 87 | // console.log('Changing Chart name'); 88 | // state.name = action.payload; 89 | // }, 90 | // changeData: (state, action) => { 91 | // console.log('changing data'); 92 | // state.data = JSON.parse(action.payload); 93 | // }, 94 | // changeXKey: (state, action) => { 95 | // console.log('Changing X Key'); 96 | // state.xKey = action.payload; 97 | // }, 98 | // changeYKey: (state, action) => { 99 | // console.log('Changing Y Key'); 100 | // state.yKey = action.payload; 101 | // }, 102 | // changeXAxisLabel: (state, action) => { 103 | // console.log('Changing X Axis Label'); 104 | // state.xAxisLabel = action.payload; 105 | // }, 106 | // changeYAxisLabel: (state, action) => { 107 | // console.log('Changing Y Axis Label'); 108 | // state.yAxisLabel = action.payload; 109 | // }, 110 | // changeHeight: (state, action) => { 111 | // if (+action.payload < 100) { 112 | // console.log( 113 | // 'Value must not be less than 100 px. Resetting to default.' 114 | // ); 115 | // action.payload = 500; 116 | // console.log(action.payload); 117 | // state.height = action.payload; 118 | // return; 119 | // } 120 | // console.log('Changing height'); 121 | // state.height = +action.payload; 122 | // }, 123 | // changeWidth: (state, action) => { 124 | // if (+action.payload < 100) { 125 | // console.log( 126 | // 'Value must not be less than 100 px. Resetting to default.' 127 | // ); 128 | // action.payload = 500; 129 | // console.log(action.payload); 130 | // state.width = action.payload; 131 | // return; 132 | // } 133 | // console.log('Changing width'); 134 | // state.width = +action.payload; 135 | // }, 136 | // // specific to histograms 137 | // changeBarPadding: (state, action) => { 138 | // console.log('Changing barPadding'); 139 | // state.barPadding = +action.payload; 140 | // }, 141 | // changeThresholds: (state, action) => { 142 | // console.log('Changing thresholds'); 143 | // state.thresholds = +action.payload; 144 | // }, 145 | // //specific to scatterplot 146 | // changeRadius: (state, action) => { 147 | // if (+action.payload < 1) { 148 | // console.log('Value must not be less than 1. Resetting to default.'); 149 | // action.payload = 5; 150 | // console.log(action.payload); 151 | // state.radius = action.payload; 152 | // return; 153 | // } 154 | // console.log('Changing radius'); 155 | // state.radius = +action.payload; 156 | // } 157 | 158 | // // Action creators are generated for each case reducer function 159 | // export const { 160 | // changeProps 161 | // // changeName, 162 | // // changeData, 163 | // // changeXKey, 164 | // // changeYKey, 165 | // // changeXAxisLabel, 166 | // // changeYAxisLabel, 167 | // // changeHeight, 168 | // // changeWidth, 169 | // // changeThresholds, 170 | // // changeBarPadding, 171 | // // changeRadius 172 | // } = propsSlice.actions; 173 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | ad3lie 11 | 12 | 13 | 14 | 15 | 26 | 27 | 28 | 29 |
30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { render } from 'react-dom'; 4 | // import App from './App.jsx'; --> Moved to Archives 5 | import AppRoutes from './AppRoutes.jsx'; 6 | import styles from './styles.css'; 7 | import { store } from './app/store'; 8 | import { Provider } from 'react-redux'; 9 | 10 | /** 11 | * AppRoutes is the generalized version that programmatically generates routes based on our 'dictionary' of chart info 12 | */ 13 | 14 | const root = createRoot(document.getElementById('root')); 15 | root.render( 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /src/mockup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | 18 | 19 | 20 | 21 | Customizer Container 22 | 23 | 24 | 25 | Chart Container 26 | 27 | 28 | 29 | Code Container 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 49 | } 52 | > 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | 66 | --> -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | @tailwind base; 3 | @tailwind components; 4 | 5 | 6 | 7 | 8 | .glassglow:hover { 9 | color: rgb(255, 255, 255); 10 | text-shadow: 0 0 10px #fff; 11 | text-shadow: 0 0 20px #fff; 12 | text-shadow: 0 0 40px #fff; 13 | text-shadow: 0 0 80px #fff; 14 | box-shadow: 0 0 40px #fff; 15 | transform: translate(0px, -2.5%); 16 | } 17 | 18 | 19 | .glass { 20 | /* From https://css.glass */ 21 | backdrop-filter: blur(8px) saturate(190%); 22 | -webkit-backdrop-filter: blur(20px) saturate(190%); 23 | background-color: rgba(249, 243, 243, 0.124); 24 | border-radius: 10px; 25 | border: 8px solid rgba(6, 6, 6, 0); 26 | } 27 | .glass2 { 28 | /* From https://css.glass */ 29 | backdrop-filter: blur(5px) saturate(190%); 30 | -webkit-backdrop-filter: blur(20px) saturate(190%); 31 | background-color: rgba(249, 243, 243, 0.19); 32 | border-radius: 22px; 33 | border: 8px solid rgba(6, 6, 6, 0); 34 | } 35 | 36 | /* Glassmorphism Carousel container effect */ 37 | .glass33 { 38 | /* From https://css.glass */ 39 | backdrop-filter: blur(20px) saturate(190%); 40 | -webkit-backdrop-filter: blur(20px) saturate(190%); 41 | background-color: rgba(243, 245, 248, 0.761); 42 | border-radius: 10px; 43 | border: 1px solid rgba(37, 21, 255, 0); 44 | } 45 | 46 | .glass3 { 47 | background: rgb(252, 252, 252); 48 | box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 ); 49 | backdrop-filter: blur( 4px ); 50 | -webkit-backdrop-filter: blur( 4px ); 51 | border-radius: 10px; 52 | border: 1px solid rgba( 255, 255, 255, 0.18 ); 53 | } 54 | /* Glassmorphism card effect */ 55 | .card { 56 | backdrop-filter: blur(16px) saturate(180%); 57 | -webkit-backdrop-filter: blur(16px) saturate(180%); 58 | background-color: rgba(250, 250, 250, 0.19); 59 | border-radius: 12px; 60 | border: 1px solid rgba(32, 31, 31, 0); 61 | } 62 | 63 | .glow-on-hover { 64 | width: 220px; 65 | height: 50px; 66 | border: none; 67 | outline: none; 68 | color: #fff; 69 | background: #111; 70 | cursor: pointer; 71 | position: relative; 72 | z-index: 0; 73 | border-radius: 10px; 74 | } 75 | 76 | body { 77 | /* background-image: url('./topy.png'); */ 78 | background-color: #1e293b; 79 | background-repeat: no-repeat; 80 | background-size: 100% 100%; 81 | padding: 1.6em 2em 4em; 82 | letter-spacing: -0.011em; 83 | font-family: 'Inter var', sans-serif; 84 | font-size: 16px; 85 | color: #34495e; 86 | } 87 | 88 | 89 | 90 | .slides { 91 | background-size: 50% 50% 92 | } 93 | 94 | .menu-icon, 95 | .fa-regular span { 96 | font-size: 18px; 97 | } 98 | 99 | 100 | 101 | /* KEEP THESE COMMENTED OUT SECTIONS HERE 102 | body{ 103 | display: flex; 104 | justify-content: center; 105 | align-items: center; 106 | flex-wrap: nowrap; 107 | min-height:100vh; 108 | background: #212534 109 | } */ 110 | /* 111 | .main{ 112 | position: relative; 113 | display: flex; 114 | justify-content: center; 115 | align-items: center; 116 | } 117 | 118 | 119 | .card{ 120 | position: relative; 121 | display:flex; 122 | height:100px; 123 | width: 250px; 124 | background-color: #0f16e969; 125 | border-radius: 15px; 126 | justify-content: center; 127 | align-items: center; 128 | flex-wrap: nowrap; 129 | flex-direction:column; 130 | text-align:center; 131 | border:2px solid rgba(99, 230, 6, 0.195); 132 | overflow:hidden; 133 | transition:.6s; 134 | box-shadow:0px 0px 100px rgba(8, 118, 128, 0.15); 135 | } 136 | 137 | .text1{ 138 | position:relative; 139 | font-size:33px; 140 | font-family:roboto; 141 | color: rgb(88 199 250); 142 | font-weight:100; 143 | letter-spacing:1px; 144 | transition:.7s; 145 | transform:translateY(110px); 146 | } 147 | 148 | .card:hover .text1{transform:translateY(-150px);opacity:0} 149 | .text2{color:#dbf9ff; 150 | font-family:roboto; 151 | font-weight:100; 152 | font-size:14px; 153 | width:100px; 154 | line-height:25px; 155 | transform:translateY(200px); 156 | transition:.7s; 157 | opacity:0 158 | } 159 | 160 | .hover{} 161 | h2{ 162 | font-size:38px; 163 | font-family:roboto; 164 | color:rgb(88 199 250); 165 | font-weight:400; 166 | margin:0; 167 | padding:0; 168 | transform:translateY(200px); 169 | opacity:1; 170 | transition:.6s; 171 | opacity:0 172 | } 173 | 174 | button{ 175 | transform:translatey(200px); 176 | transition:.88s; 177 | opacity:0; 178 | width:110px; 179 | height:38px; 180 | border-radius:40px; 181 | font-size:12px; 182 | font-family:roboto; 183 | text-transform:uppercase; 184 | font-weight:300; 185 | letter-spacing:1px; 186 | color:white; 187 | background-color: #0beef9; 188 | background-image: linear-gradient(315deg, #0beef9 0%, #48a9fe 74%); 189 | border:none; 190 | cursor:pointer 191 | } 192 | .card:hover .hover{display:block} 193 | .card:hover h2{transform:translatey(-38px);opacity:1} 194 | .card:hover .text2{transform:translatey(-20px); 195 | opacity:1} 196 | .card:hover button{transform:translatey(5px);opacity:1} 197 | .card:hover{ 198 | transform:scale(110%); 199 | box-shadow:0px 0px 100px rgb(88 199 250); 200 | } 201 | 202 | button:active{ 203 | /* .glass333 { 204 | /* From https://css.glass */ 205 | /* backdrop-filter: blur(20px) saturate(190%); 206 | -webkit-backdrop-filter: blur(20px) saturate(190%); 207 | background-color: rgb(255, 255, 255); 208 | border-radius: 22px; 209 | border: 8px solid rgba(6, 6, 6, 0); 210 | } */ 211 | 212 | 213 | /* .glass455{ */ 214 | /* From https://css.glass */ 215 | /* backdrop-filter: blur(8px) saturate(190%); */ 216 | /* -webkit-backdrop-filter: blur(20px) saturate(190%); */ 217 | /* background-color: rgba(0, 253, 253, 0.585); */ 218 | /* border-radius: 22px; */ 219 | /* border: 8px solid rgba(6, 6, 6, 0); */ 220 | /* } 221 | /* color:#0beef9 */ 222 | /* } */ 223 | -------------------------------------------------------------------------------- /src/topy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/src/topy.png -------------------------------------------------------------------------------- /src/utils/CodePreview.js: -------------------------------------------------------------------------------- 1 | import forOwn from 'lodash/forOwn'; 2 | import isPlainObject from 'lodash/isPlainObject'; 3 | import isArray from 'lodash/isArray'; 4 | import isString from 'lodash/isString'; 5 | import isNumber from 'lodash/isNumber'; 6 | import isBoolean from 'lodash/isBoolean'; 7 | import dedent from 'dedent-js'; 8 | import styled from 'styled-components'; 9 | import prettier from 'prettier/standalone'; 10 | import parserBabel from 'prettier/parser-babel'; 11 | 12 | export const indent = (content, spaces = 8) => 13 | content 14 | .split('\n') 15 | .map((line, i) => { 16 | if (i === 0) return line; 17 | return `${' '.repeat(spaces)}${line}`; 18 | }) 19 | .join('\n'); 20 | 21 | export const toJson = (value) => { 22 | const jsonString = JSON.stringify(value, null, 4); 23 | const normalized = jsonString 24 | .replace(/^(\s+)"([a-z]{1}[a-z]*)"\: /gim, (_match, space, key) => { 25 | return `${space}${key}: `; 26 | }) 27 | .replace(/"/gm, `'`); 28 | 29 | if (normalized.length < 80) { 30 | return normalized.replace(/\n/gm, ' ').replace(/\s{2,}/g, ' '); 31 | } 32 | return indent(normalized); 33 | }; 34 | 35 | export const generateChartCode = ( 36 | name, 37 | props, 38 | { dataKey, children, defaults, pkg } 39 | ) => { 40 | const properties = []; 41 | let args = ''; 42 | 43 | if (dataKey !== undefined) { 44 | properties.push(`${dataKey}={${dataKey}}`); 45 | args = `{ ${dataKey} /* see ${dataKey} from your Javascript data file */ }`; 46 | } 47 | 48 | forOwn(props, (_value, key) => { 49 | if (_value === undefined) return; 50 | if (defaults && defaults[key] === _value) return; 51 | if (key === 'theme') return; 52 | 53 | let value; 54 | if (isPlainObject(_value)) { 55 | value = `{${toJson(_value)}}`; 56 | } else if (isArray(_value)) { 57 | value = `{${toJson(_value)}}`; 58 | } else if (isString(_value)) { 59 | value = `"${_value}"`; 60 | } else if (isBoolean(_value)) { 61 | value = `{${_value ? 'true' : 'false'}}`; 62 | } else if (isNumber(_value)) { 63 | value = `{${_value}}`; 64 | } else if (typeof _value === 'function') { 65 | value = `{${indent(dedent(_value.toString()), 8)}}`; 66 | } else if (_value === null) { 67 | value = `{null}`; 68 | } else { 69 | value = _value; 70 | } 71 | 72 | properties.push(`${key}=${value}`); 73 | }); 74 | 75 | const install = `// npm install @ad3lie`; 76 | 77 | // Currently removed children imports as the user should only be importing the required chart template (ex. BarChart.jsx) and their custom data file (MyBarChart.js) in order to use the customized component (MyBarChart.jsx) 78 | // const imports = [name, ...children.map((c) => c)].map( 79 | // (i) => `import { ${i} } from 'ad3lie'` 80 | // ); 81 | const imports = [name].map((i) => `import { ${i} } from 'ad3lie'`); 82 | const importReact = `import React from 'react'` 83 | 84 | const importData = `import { ${dataKey} } from 'My${name}Data.js'`; 85 | 86 | let warning = ''; 87 | if (name) { 88 | warning = [ 89 | ``, 90 | `// Before use, remember to npm i all dependencies`, 91 | `// and the @ad3lie component library to use your charts,`, 92 | `// otherwise, no charts will be rendered.`, 93 | `// Copy the following code to your component file`, 94 | `// along with your Javascript data file.` 95 | ].join('\n'); 96 | } 97 | 98 | return `// install (please make sure versions match peerDependencies) 99 | ${install} 100 | ${importReact} 101 | ${importData} 102 | ${imports.join('\n')} 103 | ${warning} 104 | const My${name} = () => ( 105 | <${name} 106 | ${properties.join('\n ')} 107 | /> 108 | ) 109 | 110 | export default My${name}`; 111 | }; 112 | 113 | export const formatCode = (code) => { 114 | return prettier.format(code.innerText, { 115 | singleQuote: true, 116 | jsxSingleQuote: true, 117 | trailingComma: 'es5', 118 | bracketSpacing: true, 119 | bracketSameLine: true, 120 | parser: 'babel', 121 | plugins: [parserBabel] 122 | }); 123 | }; 124 | 125 | // just using styled components here only for testing html preview 126 | // our code is enclosed in an HTML tag 127 | export const CodeText = styled.code``; 128 | 129 | export const CodeBlock = styled.pre` 130 | margin: 0; 131 | font-size: 0.8rem; 132 | line-height: 1.7; 133 | padding: 12px 20px; 134 | overflow: scroll; 135 | height: 100%; 136 | `; 137 | 138 | export const Code = styled.div` 139 | position: absolute; 140 | top: 46px; 141 | bottom: 0; 142 | width: 100%; 143 | overflow: auto; 144 | `; 145 | -------------------------------------------------------------------------------- /src/utils/ExportData.js: -------------------------------------------------------------------------------- 1 | // ExportData is our current workaround to not being able to use node's fs module 2 | // (cannot run node from a browser) 3 | 4 | // converts array of Javascript objects to a string 5 | // downloadable as a .js file 6 | export const download = (filename, arr) => { 7 | let text = `export const data = [${arr 8 | .reduce((str, obj) => { 9 | return (str += 10 | '{' + Object.keys(obj).map((key) => `'${key}': ${obj[key]}`) + `}, \n`); 11 | }, '') 12 | .trim() 13 | .slice(0, -1)}]`; 14 | 15 | let element = document.createElement('a'); 16 | element.setAttribute( 17 | 'href', 18 | 'data:text/plain;charset=utf-8,' + encodeURIComponent(text) 19 | ); 20 | element.setAttribute('download', filename); 21 | 22 | element.style.display = 'none'; 23 | document.body.appendChild(element); 24 | 25 | element.click(); 26 | document.body.removeChild(element); 27 | }; 28 | 29 | // general function to download a file (taking in plain text) 30 | 31 | export const downloadCode = (filename, text) => { 32 | let element = document.createElement('a'); 33 | element.setAttribute( 34 | 'href', 35 | 'data:text/plain;charset=utf-8,' + encodeURIComponent(text) 36 | ); 37 | element.setAttribute('download', filename); 38 | 39 | element.style.display = 'none'; 40 | document.body.appendChild(element); 41 | 42 | element.click(); 43 | document.body.removeChild(element); 44 | }; 45 | -------------------------------------------------------------------------------- /src/utils/dummyTimelineData.js: -------------------------------------------------------------------------------- 1 | // This generates random fake data to show for linechart 2 | const fakeTimelineData = () => { 3 | const timelineData = [] 4 | for (let i = 0; i < 50; i++) { 5 | const obj = { 6 | x: i, 7 | y: Math.random() * 100 8 | } 9 | timelineData.push(obj) 10 | } 11 | return timelineData 12 | } 13 | 14 | export default fakeTimelineData -------------------------------------------------------------------------------- /src/utils/dummyfruitsdata.js: -------------------------------------------------------------------------------- 1 | export const sampleFruitData = [ 2 | { 3 | label: 'apples', 4 | value: 20 5 | }, 6 | { 7 | label: 'bananas', 8 | value: 40 9 | }, 10 | { 11 | label: 'pears', 12 | value: 30 13 | }, 14 | { 15 | label: 'papaya', 16 | value: 50 17 | }, 18 | { 19 | label: 'oranges', 20 | value: 70 21 | } 22 | ]; 23 | -------------------------------------------------------------------------------- /src/utils/dummypenguinsdata.js: -------------------------------------------------------------------------------- 1 | //convert this to json 2 | 3 | export const sampleData = [ 4 | { 5 | species: 'Adelie', 6 | island: 'Torgersen', 7 | culmen_length_mm: 39.2, 8 | culmen_depth_mm: 19.6, 9 | flipper_length_mm: 195, 10 | body_mass_g: 4675, 11 | sex: 'MALE' 12 | }, 13 | { 14 | species: 'Gentoo', 15 | island: 'Torgersen', 16 | culmen_length_mm: 34.1, 17 | culmen_depth_mm: 18.1, 18 | flipper_length_mm: 193, 19 | body_mass_g: 3475, 20 | sex: null 21 | }, 22 | { 23 | species: 'Emperor', 24 | island: 'Torgersen', 25 | culmen_length_mm: 42, 26 | culmen_depth_mm: 20.2, 27 | flipper_length_mm: 190, 28 | body_mass_g: 4250, 29 | sex: null 30 | }, 31 | { 32 | species: 'Fairy', 33 | island: 'Torgersen', 34 | culmen_length_mm: 37.8, 35 | culmen_depth_mm: 17.1, 36 | flipper_length_mm: 186, 37 | body_mass_g: 3300, 38 | sex: null 39 | }, 40 | { 41 | species: 'Rock', 42 | island: 'Torgersen', 43 | culmen_length_mm: 37.8, 44 | culmen_depth_mm: 17.3, 45 | flipper_length_mm: 180, 46 | body_mass_g: 3700, 47 | sex: null 48 | } 49 | ]; 50 | 51 | let xKey = 'species'; 52 | let yKey = 'body_mass_g'; 53 | 54 | const getLineChartData = (data, xKey, yKey) => { 55 | const result = []; 56 | data.reduce(function (acc, curr) { 57 | if (!acc[curr[xKey]]) { 58 | acc[curr[xKey]] = { [xKey]: curr[xKey], [yKey]: 0 }; 59 | result.push(acc[curr[xKey]]); 60 | } 61 | acc[curr[xKey]][yKey] += curr[yKey]; 62 | return acc; 63 | }, []); 64 | return result; 65 | }; 66 | // console.log(getLineChartData(penguins, 'species', 'body_mass_g')); 67 | -------------------------------------------------------------------------------- /src/utils/handlers.js: -------------------------------------------------------------------------------- 1 | // Data must be input in JSON format 2 | export const handleData = (e) => { 3 | e.preventDefault(); 4 | //Input data works for JSON format - see jsonpenguins.txt 5 | setData(JSON.parse(e.target.value)); 6 | }; 7 | 8 | // Data needs to be re-input as key changes, since grouped data is already set in state 9 | export const handleXKey = (e) => { 10 | e.preventDefault(); 11 | setXKey(e.target.value); 12 | }; 13 | 14 | export const handleYKey = (e) => { 15 | e.preventDefault(); 16 | setYKey(e.target.value); 17 | }; 18 | 19 | export const handleXAxisLabel = (e) => { 20 | e.preventDefault(); 21 | setXAxisLabel(e.target.value); 22 | }; 23 | 24 | export const handleYAxisLabel = (e) => { 25 | e.preventDefault(); 26 | setYAxisLabel(e.target.value); 27 | }; 28 | 29 | export const handleWidth = (e) => { 30 | e.preventDefault(); 31 | if (+e.target.value < 100) { 32 | console.log('Value must not be less than 100 px. Resetting to default.'); 33 | setWidth(500); 34 | return; 35 | } 36 | setWidth(+e.target.value); 37 | }; 38 | 39 | export const handleHeight = (e) => { 40 | e.preventDefault(); 41 | if (+e.target.value < 100) { 42 | console.log('Value must not be less than 100 px. Resetting to default.'); 43 | setHeight(500); 44 | return; 45 | } 46 | setHeight(+e.target.value); 47 | }; 48 | 49 | export const handleThresholds = (e) => { 50 | e.preventDefault(); 51 | setThresholds(+e.target.value); 52 | }; 53 | 54 | export const handleBarPadding = (e) => { 55 | e.preventDefault(); 56 | setBarPadding(+e.target.value); 57 | }; 58 | 59 | //below are unique to pie chart 60 | export const handleOuter = (e) => { 61 | if (+e.target.value > 100) { 62 | console.log( 63 | 'Value must not be greater than or equal to 100. Resetting to default.' 64 | ); 65 | setOuter(100); 66 | return; 67 | } 68 | setOuter(+e.target.value); 69 | }; 70 | 71 | export const handleInner = (e) => { 72 | if (+e.target.value > outerRadius) { 73 | console.log( 74 | 'Value must not be greater than or equal to size of piechart. Resetting to default.' 75 | ); 76 | setInner(0); 77 | return; 78 | } 79 | setInner(+e.target.value); 80 | }; 81 | 82 | // Data needs to be re-input as key changes, since grouped data is already set in state 83 | export const handleLabel = (e) => { 84 | e.preventDefault(); 85 | setLabel(e.target.value); 86 | }; 87 | 88 | export const handleValue = (e) => { 89 | e.preventDefault(); 90 | setValue(e.target.value); 91 | }; 92 | -------------------------------------------------------------------------------- /src/utils/hooks.js: -------------------------------------------------------------------------------- 1 | function useInterval(callback, delay) { 2 | const savedCallback = useRef(); 3 | 4 | // Remember the latest callback. 5 | useEffect(() => { 6 | savedCallback.current = callback; 7 | }); 8 | 9 | // Set up the interval. 10 | useEffect(() => { 11 | function tick() { 12 | savedCallback.current(); 13 | } 14 | if (delay !== null) { 15 | let id = setInterval(tick, delay); 16 | return () => clearInterval(id); 17 | } 18 | }, [delay]); 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/jsonFruits: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label" : "apples", 4 | "value" : 20 5 | }, 6 | { 7 | "label" : "bananas", 8 | "value" : 40 9 | }, 10 | { 11 | "label" : "pears", 12 | "value" : 30 13 | }, 14 | { 15 | "label" : "papaya", 16 | "value" : 50 17 | }, 18 | { 19 | "label" : "oranges", 20 | "value" : 70 21 | } 22 | ] -------------------------------------------------------------------------------- /src/utils/jsonLine: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": 0, 4 | "y": 93.69018434064849 5 | }, 6 | { 7 | "x": 1, 8 | "y": 46.3018492732896 9 | }, 10 | { 11 | "x": 2, 12 | "y": 58.14436737157354 13 | }, 14 | { 15 | "x": 3, 16 | "y": 54.51790586843437 17 | }, 18 | { 19 | "x": 4, 20 | "y": 22.09286874993026 21 | }, 22 | { 23 | "x": 5, 24 | "y": 85.86961518991087 25 | }, 26 | { 27 | "x": 6, 28 | "y": 89.11724125479952 29 | }, 30 | { 31 | "x": 7, 32 | "y": 9.455992643308742 33 | }, 34 | { 35 | "x": 8, 36 | "y": 20.99119171803341 37 | }, 38 | { 39 | "x": 9, 40 | "y": 82.7453493304737 41 | }, 42 | { 43 | "x": 10, 44 | "y": 50.90040434757954 45 | }, 46 | { 47 | "x": 11, 48 | "y": 14.949899532134614 49 | }, 50 | { 51 | "x": 12, 52 | "y": 52.7258770048735 53 | }, 54 | { 55 | "x": 13, 56 | "y": 1.5991168402831546 57 | }, 58 | { 59 | "x": 14, 60 | "y": 84.99314018859734 61 | }, 62 | { 63 | "x": 15, 64 | "y": 29.274661441009275 65 | }, 66 | { 67 | "x": 16, 68 | "y": 99.74242952649837 69 | }, 70 | { 71 | "x": 17, 72 | "y": 23.930472549734837 73 | }, 74 | { 75 | "x": 18, 76 | "y": 48.17435412111004 77 | }, 78 | { 79 | "x": 19, 80 | "y": 15.87766924443319 81 | }, 82 | { 83 | "x": 20, 84 | "y": 75.3447958777112 85 | }, 86 | { 87 | "x": 21, 88 | "y": 15.759655838367625 89 | }, 90 | { 91 | "x": 22, 92 | "y": 98.4290012747485 93 | }, 94 | { 95 | "x": 23, 96 | "y": 37.9983866071876 97 | }, 98 | { 99 | "x": 24, 100 | "y": 23.070307155267678 101 | }, 102 | { 103 | "x": 25, 104 | "y": 80.59082720899946 105 | }, 106 | { 107 | "x": 26, 108 | "y": 77.83023353585077 109 | }, 110 | { 111 | "x": 27, 112 | "y": 85.58448341035671 113 | }, 114 | { 115 | "x": 28, 116 | "y": 44.01333837503019 117 | }, 118 | { 119 | "x": 29, 120 | "y": 43.31288064529644 121 | }, 122 | { 123 | "x": 30, 124 | "y": 52.513463428867155 125 | }, 126 | { 127 | "x": 31, 128 | "y": 62.653139648789335 129 | }, 130 | { 131 | "x": 32, 132 | "y": 65.20603303326563 133 | }, 134 | { 135 | "x": 33, 136 | "y": 93.53359835133628 137 | }, 138 | { 139 | "x": 34, 140 | "y": 11.134986072653795 141 | }, 142 | { 143 | "x": 35, 144 | "y": 27.148267186723206 145 | }, 146 | { 147 | "x": 36, 148 | "y": 42.87113194562071 149 | }, 150 | { 151 | "x": 37, 152 | "y": 52.67505265781984 153 | }, 154 | { 155 | "x": 38, 156 | "y": 78.96405613220085 157 | }, 158 | { 159 | "x": 39, 160 | "y": 90.82843831157 161 | }, 162 | { 163 | "x": 40, 164 | "y": 34.97670270352597 165 | }, 166 | { 167 | "x": 41, 168 | "y": 76.03513540984592 169 | }, 170 | { 171 | "x": 42, 172 | "y": 20.16765664109874 173 | }, 174 | { 175 | "x": 43, 176 | "y": 91.92304733837278 177 | }, 178 | { 179 | "x": 44, 180 | "y": 42.639931564063914 181 | }, 182 | { 183 | "x": 45, 184 | "y": 13.265295678861255 185 | }, 186 | { 187 | "x": 46, 188 | "y": 17.546050260687096 189 | }, 190 | { 191 | "x": 47, 192 | "y": 50.34754937454555 193 | }, 194 | { 195 | "x": 48, 196 | "y": 65.7982470687585 197 | }, 198 | { 199 | "x": 49, 200 | "y": 76.90338996712616 201 | } 202 | ] -------------------------------------------------------------------------------- /src/utils/jsonpenguins: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "species": "Adelie", 4 | "island": "Torgersen", 5 | "culmen_length_mm": 39.2, 6 | "culmen_depth_mm": 19.6, 7 | "flipper_length_mm": 195, 8 | "body_mass_g": 4675, 9 | "sex": "MALE" 10 | }, 11 | { 12 | "species": "Gentoo", 13 | "island": "Torgersen", 14 | "culmen_length_mm": 34.1, 15 | "culmen_depth_mm": 18.1, 16 | "flipper_length_mm": 193, 17 | "body_mass_g": 3475, 18 | "sex": null 19 | }, 20 | { 21 | "species": "Emperor", 22 | "island": "Torgersen", 23 | "culmen_length_mm": 42, 24 | "culmen_depth_mm": 20.2, 25 | "flipper_length_mm": 190, 26 | "body_mass_g": 4250, 27 | "sex": null 28 | }, 29 | { 30 | "species": "Fairy", 31 | "island": "Torgersen", 32 | "culmen_length_mm": 37.8, 33 | "culmen_depth_mm": 17.1, 34 | "flipper_length_mm": 186, 35 | "body_mass_g": 3400, 36 | "sex": null 37 | }, 38 | { 39 | "species": "Rock", 40 | "island": "Torgersen", 41 | "culmen_length_mm": 37.8, 42 | "culmen_depth_mm": 17.3, 43 | "flipper_length_mm": 180, 44 | "body_mass_g": 3700, 45 | "sex": null 46 | } 47 | ] -------------------------------------------------------------------------------- /src/utils/observable.js: -------------------------------------------------------------------------------- 1 | /** Implementing the observer pattern for state management 2 | * (also bc i could do redux but im too lazy to implement that atm) 3 | */ 4 | 5 | export const makeObservable = (target) => { 6 | let listeners = []; // initial listeners can be passed an an argument as well 7 | let value = target; 8 | 9 | function get() { 10 | return value; 11 | } 12 | 13 | function set(newValue) { 14 | if (value === newValue) return; 15 | value = newValue; 16 | listeners.forEach((l) => l(value)); 17 | } 18 | 19 | function subscribe(listenerFunc) { 20 | listeners.push(listenerFunc); 21 | return () => unsubscribe(listenerFunc); // will be used inside React.useEffect 22 | } 23 | 24 | function unsubscribe(listenerFunc) { 25 | listeners = listeners.filter((l) => l !== listenerFunc); 26 | } 27 | 28 | return { 29 | get, 30 | set, 31 | subscribe 32 | }; 33 | }; 34 | 35 | export const userStore = makeObservable({ name: 'user', count: 0 }); 36 | 37 | const useUser = () => { 38 | const [user, setUser] = React.useState(userStore.get()); 39 | 40 | React.useEffect(() => { 41 | return userStore.subscribe(setUser); 42 | }, []); 43 | 44 | const actions = React.useMemo(() => { 45 | return { 46 | setName: (name) => userStore.set({ ...user, name }), 47 | incrementCount: () => userStore.set({ ...user, count: user.count + 1 }), 48 | decrementCount: () => userStore.set({ ...user, count: user.count - 1 }) 49 | }; 50 | }, [user]); 51 | 52 | return { 53 | state: user, 54 | actions 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /src/utils/parseData.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | 3 | const randomAroundMean = (mean, deviation) => 4 | mean + boxMullerRandom() * deviation; 5 | const boxMullerRandom = () => 6 | Math.sqrt(-2.0 * Math.log(Math.random())) * 7 | Math.cos(2.0 * Math.PI * Math.random()); 8 | 9 | const today = new Date(); 10 | 11 | // TYPE: DATE 12 | const formatDate = d3.timeFormat('%m/%d/%Y'); 13 | export const getTimelineData = (length = 100) => { 14 | let lastTemperature = randomAroundMean(70, 20); 15 | const firstTemperature = d3.timeDay.offset(today, -length); 16 | 17 | return new Array(length).fill(0).map((d, i) => { 18 | lastTemperature += randomAroundMean(0, 2); 19 | return { 20 | date: formatDate(d3.timeDay.offset(firstTemperature, i)), 21 | temperature: lastTemperature 22 | }; 23 | }); 24 | }; 25 | 26 | // TYPE: NUMBER 27 | export const getScatterData = (count = 100) => 28 | new Array(count).fill(0).map((d, i) => ({ 29 | temperature: randomAroundMean(70, 20), 30 | humidity: randomAroundMean(0.5, 0.1) 31 | })); 32 | 33 | // ============================================================================================================= // 34 | 35 | import { userEnteredData, userAxisData } from './EnteredData.jsx'; 36 | 37 | // TYPE: NUMBER/STRING ---- BAR CHART 38 | // export const getBarChartData = (xKey, yKey, arr) => { 39 | // // // Three Steps 40 | // // // 1. Create a Set of Unique Keys 41 | // let set = new Set() 42 | // for (const obj of arr) { 43 | // set.push(obj[xKey]); 44 | // } 45 | // // 2. Create an Array to hold the Accumulated Results 46 | // let aggs = [] 47 | // // 3. Iterate over the set, and use Map and Reduce to create an obj for each xKey 48 | // for (const key of set) { 49 | // const agg = arr.map(obj => key === obj[xKey]).reduce((acc, curr) => { 50 | // return acc + curr[xKey] 51 | // }, 0) 52 | // aggs.push(agg); 53 | // } 54 | // return aggs 55 | // } 56 | 57 | // console.log(getBarChartData("species","body_mass_g",userEnteredData)) 58 | 59 | // REDUCE GOD 60 | // const test = (xKey, yKey, data) => { 61 | // const arrOfObj = Array.from( 62 | // data.reduce((m, { xKey, yKey }) => 63 | // m.set(xKey, (m.get(xKey) || 0) + yKey), 64 | // new Map() 65 | // ), 66 | // ([xKey, yKey]) => ({ xKey, yKey }) 67 | // ); 68 | 69 | // return arrOfObj; 70 | // } 71 | 72 | export const getBarChartData = (data, xKey, yKey) => { 73 | const arrOfObj = Array.from( 74 | data 75 | .reduce((acc, { value, ...r }) => { 76 | const key = JSON.stringify(r); 77 | // console.log('key', key); 78 | const current = acc.get(key) || { ...r }; 79 | return acc.set(key, { ...current }); 80 | }, new Map()) 81 | .values() 82 | ); 83 | return arrOfObj; 84 | }; 85 | 86 | // Works for reducing an array of objects, grouped by two keys 87 | /** Ex. Group by Species and Body_mass_g 88 | * [ { species: 'Gentoo', body_mass_g: 392350 }, 89 | { species: 'Adelie', body_mass_g: 10100 } ] 90 | */ 91 | const sampleData = [ 92 | { 93 | species: 'Adelie', 94 | island: 'Torgersen', 95 | culmen_length_mm: 39.2, 96 | culmen_depth_mm: 19.6, 97 | flipper_length_mm: 195, 98 | body_mass_g: 4675, 99 | sex: 'MALE' 100 | }, 101 | { 102 | species: 'Gentoo', 103 | island: 'Torgersen', 104 | culmen_length_mm: 34.1, 105 | culmen_depth_mm: 18.1, 106 | flipper_length_mm: 193, 107 | body_mass_g: 3475, 108 | sex: null 109 | }, 110 | { 111 | species: 'Emperor', 112 | island: 'Torgersen', 113 | culmen_length_mm: 42, 114 | culmen_depth_mm: 20.2, 115 | flipper_length_mm: 190, 116 | body_mass_g: 4250, 117 | sex: null 118 | }, 119 | { 120 | species: 'Chinstrap', 121 | island: 'Torgersen', 122 | culmen_length_mm: 37.8, 123 | culmen_depth_mm: 17.1, 124 | flipper_length_mm: 186, 125 | body_mass_g: 3300, 126 | sex: null 127 | }, 128 | { 129 | species: 'Emperor', 130 | island: 'Torgersen', 131 | culmen_length_mm: 37.8, 132 | culmen_depth_mm: 17.3, 133 | flipper_length_mm: 180, 134 | body_mass_g: 3700, 135 | sex: null 136 | } 137 | ]; 138 | 139 | export const getBarChartData2 = (data, xKey, yKey) => { 140 | // const data = JSON.parse(stringifiedData); 141 | const result = []; 142 | data.reduce(function (acc, curr) { 143 | if (!acc[curr[xKey]]) { 144 | acc[curr[xKey]] = { [xKey]: curr[xKey], [yKey]: 0 }; 145 | result.push(acc[curr[xKey]]); 146 | } 147 | acc[curr[xKey]][yKey] += curr[yKey]; 148 | return acc; 149 | }, []); 150 | console.log(result); 151 | return result; 152 | }; 153 | 154 | // console.log(getBarChartData2(sampleData, 'species', 'body_mass_g')); 155 | 156 | // console.log( 157 | // 'this is reduce BarChartData', 158 | // getBarChartData('species', 'body_mass_g', userEnteredData) 159 | // ); 160 | 161 | // const penguins = Array.from( 162 | // data.reduce( 163 | // (m, { species, body_mass_g }) => 164 | // m.set(species, (m.get(species) || 0) + body_mass_g), 165 | // new Map() 166 | // ), 167 | // ([species, body_mass_g]) => ({ species, body_mass_g }) 168 | // ); 169 | 170 | // console.log('arr: ', arr) 171 | // console.log('xKey ', xKey) 172 | // // console.log(' ', ) 173 | // const cache = {} 174 | // const result = arr.reduce((acc, curr) => { 175 | // console.log('acc', acc) 176 | // if (curr[xKey] in cache) { 177 | // return acc[cache[xKey]][yKey] += curr[yKey] 178 | // } 179 | // // else add to cache and accumulator 180 | // // ex) curr[xKey] = "Adelie" 181 | // // cache["Adelie"] = length of cache properties ????????? 182 | // cache[curr[xKey]] = Object.keys(cache).length; 183 | // return acc.push(curr) 184 | // }, []) 185 | // return resut; 186 | // } 187 | // reached algos part of the project :upside_down_face:. Given an array of objects, how do you find the total for each unique x-value? 188 | // Result should be [{“species”: “Adelie”, “body_mass_g”: 7200}, {“species”: “Gentoo”, “body_mass_g”: 10950}] 189 | /* 190 | [ 191 | { 192 | "species": "Adelie", 193 | "body_mass_g": 3750, 194 | }, 195 | { 196 | "species": "Adelie", 197 | "body_mass_g": 3450, 198 | }, 199 | { 200 | "species": "Gentoo", 201 | "body_mass_g": 5750, 202 | }, 203 | { 204 | "species": "Gentoo", 205 | "body_mass_g": 5200, 206 | } 207 | ] 208 | */ 209 | 210 | // TYPE: NUMBERS ---- LINE GRAPHS (ORDERED) 211 | 212 | export const getUONumData = ( 213 | userAxis = userAxisData, 214 | count = userEnteredData.length 215 | ) => { 216 | return new Array(count).fill({}).map((d, i) => ({ 217 | [userAxis['x']]: userEnteredData[i][userAxis['x']], 218 | [userAxis['y']]: userEnteredData[i][userAxis['y']] 219 | })); 220 | }; 221 | 222 | // TYPE: DATE ---- TIMELINE 223 | export const getTimelineData3 = ( 224 | length = userEnteredData.length, 225 | userAxis = userAxisData 226 | ) => { 227 | return new Array(length).fill({}).map((d, i) => ({ 228 | [userAxis['x']]: new Date(userEnteredData[i][userAxis['x']]), 229 | [userAxis['y']]: userEnteredData[i][userAxis['y']] 230 | })); 231 | }; 232 | // console.log('gettimelineData', getTimelineData3()); 233 | 234 | // TYPE: NUMBERS ---- LINE GRAPHS (ORDERED) 235 | const objectComparisonCallback = (arrayItemA, arrayItemB) => { 236 | if (arrayItemA[userAxisData['x']] < arrayItemB[userAxisData['x']]) return -1; 237 | if (arrayItemA[userAxisData['x']] > arrayItemB[userAxisData['x']]) return 1; 238 | return 0; 239 | }; 240 | userEnteredData.sort(objectComparisonCallback); 241 | 242 | export const getNumbersData = ( 243 | userAxis = userAxisData, 244 | count = userEnteredData.length 245 | ) => { 246 | return new Array(count).fill({}).map((d, i) => ({ 247 | [userAxis['x']]: userEnteredData[i][userAxis['x']], 248 | [userAxis['y']]: userEnteredData[i][userAxis['y']] 249 | })); 250 | }; 251 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import { useEffect, useState, useRef } from 'react'; 3 | import ResizeObserver from 'resize-observer-polyfill'; 4 | 5 | export const accessorPropsType = PropTypes.oneOfType([ 6 | PropTypes.func, 7 | PropTypes.number 8 | ]); 9 | 10 | export const useAccessor = (accessor, d, i) => 11 | typeof accessor == 'function' ? accessor(d, i) : accessor; 12 | 13 | export const dimensionsPropsType = PropTypes.shape({ 14 | height: PropTypes.number, 15 | width: PropTypes.number, 16 | marginTop: PropTypes.number, 17 | marginRight: PropTypes.number, 18 | marginBottom: PropTypes.number, 19 | marginLeft: PropTypes.number 20 | }); 21 | 22 | export const combineChartDimensions = (dimensions) => { 23 | let parsedDimensions = { 24 | marginTop: 40, 25 | marginRight: 30, 26 | marginBottom: 40, 27 | marginLeft: 75, 28 | ...dimensions 29 | }; 30 | 31 | // console.log(parsedDimensions); 32 | 33 | return { 34 | ...parsedDimensions, 35 | boundedHeight: Math.max( 36 | parsedDimensions.height - 37 | parsedDimensions.marginTop - 38 | parsedDimensions.marginBottom, 39 | 0 40 | ), 41 | boundedWidth: Math.max( 42 | parsedDimensions.width - 43 | parsedDimensions.marginLeft - 44 | parsedDimensions.marginRight, 45 | 0 46 | ) 47 | }; 48 | }; 49 | 50 | export const useChartDimensions = (passedSettings) => { 51 | const ref = useRef(); 52 | const dimensions = combineChartDimensions(passedSettings); 53 | 54 | if (dimensions.width && dimensions.height) return [ref, dimensions]; 55 | 56 | const [width, changeWidth] = useState(0); 57 | const [height, changeHeight] = useState(0); 58 | 59 | useEffect(() => { 60 | const element = ref.current; 61 | const resizeObserver = new ResizeObserver((entries) => { 62 | if (!Array.isArray(entries)) return; 63 | if (!entries.length) return; 64 | 65 | const entry = entries[0]; 66 | 67 | if (width != entry.contentRect.width) 68 | changeWidth(entry.contentRect.width); 69 | if (height != entry.contentRect.height) 70 | changeHeight(entry.contentRect.height); 71 | }); 72 | 73 | resizeObserver.observe(element); 74 | 75 | return () => resizeObserver.unobserve(element); 76 | }, []); 77 | 78 | const newSettings = combineChartDimensions({ 79 | ...dimensions, 80 | width: dimensions.width || width, 81 | height: dimensions.height || height 82 | }); 83 | 84 | return [ref, newSettings]; 85 | }; 86 | 87 | let lastId = 0; 88 | export const useUniqueId = (prefix = '') => { 89 | lastId++; 90 | return [prefix, lastId].join('-'); 91 | }; 92 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // import PropTypes from 'prop-types'; 4 | import { useEffect, useState, useRef } from 'react'; 5 | import ResizeObserver from 'resize-observer-polyfill'; 6 | 7 | export type accessorPropsType = Function | number; 8 | // export const accessorPropsType = ( 9 | // PropTypes.oneOfType([ 10 | // PropTypes.func, 11 | // PropTypes.number, 12 | // ]) 13 | // ) 14 | 15 | export const useAccessor = ( 16 | accessor: (arg0: any, arg1: any) => any, 17 | d: any, 18 | i: any 19 | ) => (typeof accessor === 'function' ? accessor(d, i) : accessor); 20 | // export const useAccessor = (accessor, d, i) => ( 21 | // typeof accessor == "function" ? accessor(d, i) : accessor 22 | // ) 23 | 24 | // export const dimensionsPropsType = ( 25 | // PropTypes.shape({ 26 | // height: PropTypes.number, 27 | // width: PropTypes.number, 28 | // marginTop: PropTypes.number, 29 | // marginRight: PropTypes.number, 30 | // marginBottom: PropTypes.number, 31 | // marginLeft: PropTypes.number, 32 | // }) 33 | // ) 34 | 35 | export interface DimensionsPropsType { 36 | height: number; 37 | width: number; 38 | marginTop: number; 39 | marginRight: number; 40 | marginBottom: number; 41 | marginLeft: number; 42 | boundedHeight: number; 43 | boundedWidth: number; 44 | } 45 | 46 | export const combineChartDimensions = (dimensions: any) => { 47 | let parsedDimensions = { 48 | marginTop: 40, 49 | marginRight: 30, 50 | marginBottom: 40, 51 | marginLeft: 75, 52 | ...dimensions 53 | }; 54 | 55 | return { 56 | ...parsedDimensions, 57 | boundedHeight: Math.max( 58 | parsedDimensions.height - 59 | parsedDimensions.marginTop - 60 | parsedDimensions.marginBottom, 61 | 0 62 | ), 63 | boundedWidth: Math.max( 64 | parsedDimensions.width - 65 | parsedDimensions.marginLeft - 66 | parsedDimensions.marginRight, 67 | 0 68 | ) 69 | }; 70 | }; 71 | 72 | export const useChartDimensions = (passedSettings: { 73 | marginBottom: number; 74 | }) => { 75 | const ref = useRef(); 76 | const dimensions = combineChartDimensions(passedSettings); 77 | 78 | if (dimensions.width && dimensions.height) return [ref, dimensions]; 79 | 80 | const [width, changeWidth] = useState(0); 81 | const [height, changeHeight] = useState(0); 82 | 83 | 84 | useEffect(() => { 85 | const element = ref.current; 86 | const resizeObserver = new ResizeObserver((entries: string | any[]) => { 87 | if (!Array.isArray(entries)) return; 88 | if (!entries.length) return; 89 | 90 | const entry = entries[0]; 91 | 92 | if (width != entry.contentRect.width) 93 | changeWidth(entry.contentRect.width); 94 | if (height != entry.contentRect.height) 95 | changeHeight(entry.contentRect.height); 96 | }); 97 | 98 | resizeObserver.observe(element); 99 | 100 | return () => resizeObserver.unobserve(element); 101 | }, []); 102 | 103 | const newSettings = combineChartDimensions({ 104 | ...dimensions, 105 | width: dimensions.width || width, 106 | height: dimensions.height || height 107 | }); 108 | 109 | return [ref, newSettings]; 110 | }; 111 | 112 | let lastId = 0; 113 | export const useUniqueId = (prefix = '') => { 114 | lastId++; 115 | return [prefix, lastId].join('-'); 116 | }; 117 | -------------------------------------------------------------------------------- /stats.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/stats.json -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | `./index.html'`, 4 | './src/**/*.{html,js,jsx}', 5 | './node_modules/tw-elements/dist/js/**/*.js', 6 | './components/**/*.{html,jsx}', 7 | 8 | ], 9 | theme: { 10 | extend: { 11 | gridTemplateRows: { 12 | // Complex site-specific row configuration 13 | main: 'repeat(2, minmax(0, 1fr)) 50px' 14 | }, 15 | maxHeight: { 16 | 'chart-container': '95vh', 17 | } 18 | } 19 | }, 20 | plugins: [require('tw-elements/dist/plugin')] 21 | }; 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Ensure that .d.ts files are created by tsc, but not .js files 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | // Ensure that Babel can safely transpile files in the TypeScript project 7 | "isolatedModules": true, 8 | "esModuleInterop": true, 9 | "jsx": "react", 10 | "target": "es6", 11 | "strict": true, 12 | "lib": ["dom"], 13 | "moduleResolution": "node" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /webpack-stats.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ad3lie/03280e410826acb39ef923ddbedba37ea2a03fa2/webpack-stats.json -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); 3 | // const BundleAnalyzerPlugin = 4 | // require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 5 | const path = require('path'); 6 | 7 | module.exports = [{ 8 | mode: 'development', 9 | entry: './src/index.jsx', 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: 'bundle.js' 13 | }, 14 | // devtool: "source-map", 15 | resolve: { 16 | extensions: ['.tsx', '.ts', '.js', '.jsx', '.json', '.css', '.scss'], 17 | modules: ['src', 'node_modules'], 18 | fallback: { 19 | fs: false, 20 | path: require.resolve('path-browserify') 21 | } 22 | }, 23 | optimization: { 24 | usedExports: true, 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.jsx?/, 30 | exclude: /node_modules/, 31 | use: { 32 | loader: 'babel-loader', 33 | options: { 34 | presets: ['@babel/preset-env', '@babel/preset-react'] 35 | } 36 | } 37 | }, 38 | { 39 | test: /.js$/, 40 | exclude: /node_modules/, 41 | use: { 42 | loader: 'babel-loader', 43 | options: { 44 | presets: ['@babel/preset-env', '@babel/preset-react'] 45 | } 46 | } 47 | }, 48 | { 49 | test: /\.tsx?$/, 50 | exclude: /node_modules/, 51 | use: { 52 | loader: 'babel-loader', 53 | options: { 54 | presets: [ 55 | '@babel/preset-env', 56 | '@babel/preset-react', 57 | '@babel/preset-typescript' 58 | ] 59 | } 60 | } 61 | }, 62 | 63 | { 64 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 65 | type: 'asset/resource' 66 | }, 67 | { 68 | test: /.+\.css$/i, 69 | // exclude: /node_modules/, 70 | use: [ 71 | 'style-loader', 72 | 'css-loader', 73 | { 74 | loader: 'postcss-loader', 75 | options: { 76 | postcssOptions: { 77 | plugins: { 78 | tailwindcss: {}, 79 | autoprefixer: {} 80 | } 81 | } 82 | } 83 | } 84 | ] 85 | } 86 | ] 87 | }, 88 | 89 | plugins: [ 90 | new HtmlWebpackPlugin({ 91 | template: './src/index.html' 92 | }), 93 | new NodePolyfillPlugin() 94 | ], 95 | devServer: { 96 | static: path.resolve(__dirname, 'dist'), 97 | port: 8080 98 | } 99 | }] 100 | --------------------------------------------------------------------------------