├── js-client
├── index.css
├── index.html
└── index.js
└── react-client
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.js
├── App.test.js
├── BarChart.js
├── PolarArea.js
├── RadarChart.js
├── ScatterPlot.js
├── index.js
└── reportWebVitals.js
└── yarn.lock
/js-client/index.css:
--------------------------------------------------------------------------------
1 | .barchart {
2 | font-size: 9px;
3 | line-height: 15px;
4 | table-layout: fixed;
5 | text-align: center;
6 | width: 100%;
7 | height: 226px;
8 | }
9 |
10 | .barchart tr:nth-child(1) td {
11 | vertical-align: bottom;
12 | height: 200px;
13 | }
14 |
15 | .barchart .bar {
16 | background: #0d4aa5;
17 | padding: 10px 2px 0;
18 | }
19 |
20 | .barchart .label {
21 | background-color: black;
22 | margin-top: -30px;
23 | padding: 0 3px;
24 | color: white;
25 | border-radius: 4px;
26 | }
27 |
28 | .minwidth {
29 | width: 500px;
30 | }
--------------------------------------------------------------------------------
/js-client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vanilla JS Data Visualization Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 50
16 |
17 | |
18 |
19 | 5000
20 |
21 | |
22 |
23 | 5
24 |
25 | |
26 |
27 | 5
28 |
29 | |
30 |
31 | 50
32 |
33 | |
34 |
35 | 50
36 |
37 | |
38 |
39 | 5
40 |
41 | |
42 |
43 | 5
44 |
45 | |
46 |
47 | 50
48 |
49 | |
50 |
51 | 50
52 |
53 | |
54 |
55 | 5
56 |
57 | |
58 |
59 |
60 | 00 |
61 | 01 |
62 | 02 |
63 | 03 |
64 | 04 |
65 | 05 |
66 | 06 |
67 | 07 |
68 | 08 |
69 | 09 |
70 | 10 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/js-client/index.js:
--------------------------------------------------------------------------------
1 | var data = [
2 | {
3 | "year": "2010",
4 | "amount": 134
5 | },
6 | {
7 | "year": "2011",
8 | "amount": 156
9 | },
10 | {
11 | "year": "2012",
12 | "amount": 190
13 | },
14 | {
15 | "year": "2013",
16 | "amount": 100
17 | },
18 | {
19 | "year": "2014",
20 | "amount": 138
21 | },
22 | {
23 | "year": "2015",
24 | "amount": 165
25 | },
26 | {
27 | "year": "2016",
28 | "amount": 84
29 | },
30 | {
31 | "year": "2017",
32 | "amount": 122
33 | },
34 | {
35 | "year": "2018",
36 | "amount": 167
37 | },
38 | {
39 | "year": "2019",
40 | "amount": 198
41 | },
42 | {
43 | "year": "2020",
44 | "amount": 176
45 | }
46 | ]
47 |
48 | async function callApis() {
49 | const users = await fetch("https://reqres.in/api/users")
50 | const products = await fetch("https://fakestoreapi.com/products")
51 | const posts = await fetch('https://jsonplaceholder.typicode.com/posts')
52 |
53 | await Promise.all([users, products, posts]).then(responses => {
54 | return responses.map(res => res.json())
55 | }).then(data => console.log(data))
56 | }
57 |
58 | callApis()
59 |
60 | fetch('https://jsonplaceholder.typicode.com/posts')
61 | .then(async (response) => {
62 | fetchedData = await response.json()
63 | var svg = d3.select("#data-vis-container")
64 |
65 | var margin = 200,
66 | width = svg.attr("width") - margin,
67 | height = svg.attr("height") - margin
68 |
69 | svg.append("text")
70 | .attr("transform", "translate(100,0)")
71 | .attr("x", 50)
72 | .attr("y", 50)
73 | .attr("font-size", "24px")
74 | .text("Example fluctuations")
75 |
76 | var xScale = d3.scaleBand().range([0, width]).padding(0.4),
77 | yScale = d3.scaleLinear().range([height, 0]);
78 |
79 | var g = svg.append("g")
80 | .attr("transform", "translate(" + 100 + "," + 100 + ")");
81 |
82 | xScale.domain(fetchedData.map(function (d) { return d.userId; }));
83 | yScale.domain([0, d3.max(fetchedData, function (d) { return d.id; })]);
84 |
85 | g.append("g")
86 | .attr("transform", "translate(0," + height + ")")
87 | .call(d3.axisBottom(xScale))
88 | .append("text")
89 | .attr("y", height - 250)
90 | .attr("x", width - 100)
91 | .attr("text-anchor", "end")
92 | .attr("stroke", "black")
93 | .text("Year");
94 |
95 | g.append("g")
96 | .call(d3.axisLeft(yScale).tickFormat(function (d) {
97 | return "*" + d;
98 | })
99 | .ticks(10))
100 | .append("text")
101 | .attr("transform", "rotate(-90)")
102 | .attr("y", 6)
103 | .attr("dy", "-5.1em")
104 | .attr("text-anchor", "end")
105 | .attr("stroke", "black")
106 | .text("Amount");
107 |
108 | g.selectAll(".bar")
109 | .data(fetchedData)
110 | .enter().append("rect")
111 | .attr("class", "bar")
112 | .attr("x", function (d) { return xScale(d.userId); })
113 | .attr("y", function (d) { return yScale(d.id); })
114 | .attr("width", xScale.bandwidth())
115 | .attr("height", function (d) { return height - yScale(d.id); })
116 | .attr("fill", "#0d4aa5");
117 | })
118 |
119 |
--------------------------------------------------------------------------------
/react-client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/react-client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `yarn start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `yarn test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `yarn build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `yarn eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `yarn build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/react-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "@visx/group": "^2.1.0",
10 | "@visx/mock-data": "^2.1.2",
11 | "@visx/scale": "^2.2.2",
12 | "@visx/shape": "^2.2.2",
13 | "chart.js": "^3.7.0",
14 | "d3": "^7.1.1",
15 | "faker": "^6.6.6",
16 | "react": "^17.0.2",
17 | "react-chartjs-2": "^4.0.1",
18 | "react-dom": "^17.0.2",
19 | "react-scripts": "4.0.3",
20 | "web-vitals": "^1.0.1"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test",
26 | "eject": "react-scripts eject"
27 | },
28 | "eslintConfig": {
29 | "extends": [
30 | "react-app",
31 | "react-app/jest"
32 | ]
33 | },
34 | "browserslist": {
35 | "production": [
36 | ">0.2%",
37 | "not dead",
38 | "not op_mini all"
39 | ],
40 | "development": [
41 | "last 1 chrome version",
42 | "last 1 firefox version",
43 | "last 1 safari version"
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/react-client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flippedcoder/data-vis/920887f5ebad9670de69f48c77d9ad77c76f054b/react-client/public/favicon.ico
--------------------------------------------------------------------------------
/react-client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/react-client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flippedcoder/data-vis/920887f5ebad9670de69f48c77d9ad77c76f054b/react-client/public/logo192.png
--------------------------------------------------------------------------------
/react-client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flippedcoder/data-vis/920887f5ebad9670de69f48c77d9ad77c76f054b/react-client/public/logo512.png
--------------------------------------------------------------------------------
/react-client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/react-client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/react-client/src/App.js:
--------------------------------------------------------------------------------
1 | import { RadarChart } from './RadarChart';
2 | import PolarAreaChart from './PolarArea';
3 | import ScatterPlot from './ScatterPlot';
4 | import BarChart from './BarChart';
5 | import { useEffect } from 'react';
6 |
7 | const rawData = [
8 | { group: 'Captain America', axis: 'Intelligence', value: 3, description: 'only human' },
9 | { group: 'Captain America', axis: 'Strength', value: 3, description: 'only human' },
10 | { group: 'Captain America', axis: 'Speed', value: 2, description: 'only human' },
11 | { group: 'Captain America', axis: 'Durability', value: 3, description: 'only human' },
12 | { group: 'Captain America', axis: 'Energy', value: 1, description: 'only human' },
13 | { group: 'Captain America', axis: 'Fighting Skills', value: 6, description: 'able to judge combat decisively' },
14 | { group: 'Iron Man', axis: 'Intelligence', value: 6, description: 'Smart entreprenuer' },
15 | { group: 'Iron Man', axis: 'Strength', value: 6, description: 'Powered by his suit' },
16 | { group: 'Iron Man', axis: 'Speed', value: 5, description: 'rocket boosters' },
17 | { group: 'Iron Man', axis: 'Durability', value: 6, description: 'tough durable material' },
18 | { group: 'Iron Man', axis: 'Energy', value: 6, description: '' },
19 | { group: 'Iron Man', axis: 'Fighting Skills', value: 4, description: '' },
20 | { group: 'Hulk', axis: 'Intelligence', value: 6, description: 'Scientist brilliance' },
21 | { group: 'Hulk', axis: 'Strength', value: 7, description: 'Insanely strong' },
22 | { group: 'Hulk', axis: 'Speed', value: 3, description: 'clumsy' },
23 | { group: 'Hulk', axis: 'Durability', value: 7, description: 'Close to industructible' },
24 | { group: 'Hulk', axis: 'Energy', value: 1, description: '' },
25 | { group: 'Hulk', axis: 'Fighting Skills', value: 4, description: 'great at SMASHING' },
26 | { group: 'Thor', axis: 'Intelligence', value: 2, description: 'not too bright' },
27 | { group: 'Thor', axis: 'Strength', value: 7, description: 'god-like strength' },
28 | { group: 'Thor', axis: 'Speed', value: 7, description: 'god-like speed' },
29 | { group: 'Thor', axis: 'Durability', value: 6, description: 'god-like durability' },
30 | { group: 'Thor', axis: 'Energy', value: 6, description: '' },
31 | { group: 'Thor', axis: 'Fighting Skills', value: 4, description: 'quite low for a god???' },
32 | ]
33 |
34 | const shapeData = (input) => {
35 | let data = [];
36 | let groups = []; // track unique groups
37 | input.forEach(function (record) {
38 | let group = record.group;
39 | if (groups.indexOf(group) < 0) {
40 | groups.push(group); // push to unique groups tracking
41 | data.push({ // push group node in data
42 | group: group,
43 | axes: []
44 | });
45 | };
46 | data.forEach(function (d) {
47 | if (d.group === record.group) { // push record data into right group in data
48 | d.axes.push({
49 | axis: record.axis,
50 | value: parseInt(record.value),
51 | description: record.description
52 | });
53 | }
54 | });
55 | });
56 | return data;
57 | }
58 |
59 | function App() {
60 | // const radarData = shapeData(rawData)
61 |
62 | // useEffect(() => {
63 | // RadarChart.draw('#radar', radarData)
64 | // }, [radarData])
65 |
66 | return (
67 | <>
68 | {/* Radar Chart
69 |
70 |
71 | */}
72 |
73 | >
74 | )
75 | }
76 |
77 | export default App;
--------------------------------------------------------------------------------
/react-client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/react-client/src/BarChart.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Group } from "@visx/group";
3 | import { Bar } from "@visx/shape";
4 | import { scaleLinear, scaleBand } from "@visx/scale";
5 |
6 | const data = [{
7 | "year": "2010",
8 | "amount": 134
9 | },
10 | {
11 | "year": "2011",
12 | "amount": 156
13 | },
14 | {
15 | "year": "2012",
16 | "amount": 190
17 | },
18 | {
19 | "year": "2013",
20 | "amount": 100
21 | },
22 | {
23 | "year": "2014",
24 | "amount": 138
25 | },
26 | {
27 | "year": "2015",
28 | "amount": 165
29 | },
30 | {
31 | "year": "2016",
32 | "amount": 84
33 | },
34 | {
35 | "year": "2017",
36 | "amount": 122
37 | },
38 | {
39 | "year": "2018",
40 | "amount": 167
41 | },
42 | {
43 | "year": "2019",
44 | "amount": 198
45 | },
46 | {
47 | "year": "2020",
48 | "amount": 176
49 | }
50 | ];
51 |
52 | const width = 500;
53 | const height = 300;
54 | const margin = { top: 20, bottom: 20, left: 20, right: 20 };
55 | const xMax = width - margin.left - margin.right;
56 | const yMax = height - margin.top - margin.bottom;
57 |
58 | const x = (d) => d.year;
59 | const y = (d) => +d.amount;
60 |
61 | const xScale = scaleBand({
62 | range: [0, xMax],
63 | round: true,
64 | domain: data.map(x),
65 | padding: 0.4
66 | });
67 |
68 | const yScale = scaleLinear({
69 | range: [yMax, 0],
70 | round: true,
71 | domain: [0, Math.max(...data.map(y))]
72 | });
73 |
74 | const compose = (scale, accessor) => (data) => scale(accessor(data));
75 | const xPoint = compose(xScale, x);
76 | const yPoint = compose(yScale, y);
77 |
78 | function BarChart() {
79 | return (
80 |
96 | );
97 | }
98 |
99 | export default BarChart
--------------------------------------------------------------------------------
/react-client/src/PolarArea.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Chart as ChartJS,
4 | RadialLinearScale,
5 | ArcElement,
6 | Tooltip,
7 | Legend,
8 | } from 'chart.js';
9 | import { PolarArea } from 'react-chartjs-2';
10 |
11 | ChartJS.register(RadialLinearScale, ArcElement, Tooltip, Legend);
12 |
13 | export const data = {
14 | labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
15 | datasets: [
16 | {
17 | label: '# of Votes',
18 | data: [12, 19, 3, 5, 2, 3],
19 | backgroundColor: [
20 | 'rgba(255, 99, 132, 0.5)',
21 | 'rgba(54, 162, 235, 0.5)',
22 | 'rgba(255, 206, 86, 0.5)',
23 | 'rgba(75, 192, 192, 0.5)',
24 | 'rgba(153, 102, 255, 0.5)',
25 | 'rgba(255, 159, 64, 0.5)',
26 | ],
27 | borderWidth: 1,
28 | },
29 | ],
30 | };
31 |
32 | export default function PolarAreaChart() {
33 | return ;
34 | }
35 |
--------------------------------------------------------------------------------
/react-client/src/RadarChart.js:
--------------------------------------------------------------------------------
1 | import * as d3 from 'd3';
2 |
3 | export const RadarChart = {
4 | draw: function(id, data, options) {
5 | // add touch to mouseover and mouseout
6 | let over = "ontouchstart" in window ? "touchstart" : "mouseover";
7 | let out = "ontouchstart" in window ? "touchend" : "mouseout";
8 |
9 | /** Initiate default configuration parameters and vis object
10 | *
11 | **/
12 | // initiate default config
13 | const w = 300;
14 | const h = 300;
15 |
16 | const config = {
17 | w: w,
18 | h: h,
19 | facet: false,
20 | levels: 5,
21 | levelScale: 0.85,
22 | labelScale: 1.0,
23 | facetPaddingScale: 2.5,
24 | maxValue: 0,
25 | radians: 2 * Math.PI,
26 | polygonAreaOpacity: 0.3,
27 | polygonStrokeOpacity: 1,
28 | polygonPointSize: 4,
29 | legendBoxSize: 10,
30 | translateX: w / 4,
31 | translateY: h / 4,
32 | paddingX: w,
33 | paddingY: h,
34 | colors: d3.scaleOrdinal(d3.schemeCategory10),
35 | showLevels: true,
36 | showLevelsLabels: true,
37 | showAxesLabels: true,
38 | showAxes: true,
39 | showLegend: true,
40 | showVertices: true,
41 | showPolygons: true
42 | };
43 |
44 | // initiate main vis component
45 | let vis = {
46 | svg: null,
47 | tooltip: null,
48 | levels: null,
49 | axis: null,
50 | vertices: null,
51 | legend: null,
52 | allAxis: null,
53 | total: null,
54 | radius: null
55 | };
56 |
57 | // feed user configuration options
58 | if ("undefined" !== typeof options) {
59 | for (let i in options) {
60 | if ("undefined" !== typeof options[i]) {
61 | config[i] = options[i];
62 | }
63 | }
64 | }
65 |
66 | render(data); // render the visualization
67 |
68 | /** helper functions
69 | *
70 | * @function: render: render the visualization
71 | * @function: updateConfig: update configuration parameters
72 | * @function: buildVis: build visualization using the other build helper functions
73 | * @function: buildVisComponents: build main vis components
74 | * @function: buildLevels: build "spiderweb" levels
75 | * @function: buildLevelsLabels: build out the levels labels
76 | * @function: buildAxes: builds out the axes
77 | * @function: buildAxesLabels: builds out the axes labels
78 | * @function: buildCoordinates: builds [x, y] coordinates of polygon vertices.
79 | * @function: buildPolygons: builds out the polygon areas of the dataset
80 | * @function: buildVertices: builds out the polygon vertices of the dataset
81 | * @function: buildLegend: builds out the legend
82 | **/
83 |
84 | // render the visualization
85 | function render(data) {
86 | // remove existing svg if exists
87 | d3.select(id).selectAll("svg").remove();
88 | updateConfig();
89 |
90 | if (config.facet) {
91 | data.forEach(function(d, i) {
92 | buildVis([d]); // build svg for each data group
93 |
94 | // override colors
95 | vis.svg.selectAll(".polygon-areas")
96 | .attr("stroke", config.colors(i))
97 | .attr("fill", config.colors(i));
98 | vis.svg.selectAll(".polygon-vertices")
99 | .attr("fill", config.colors(i));
100 | vis.svg.selectAll(".legend-tiles")
101 | .attr("fill", config.colors(i));
102 | });
103 | } else {
104 | buildVis(data); // build svg
105 | }
106 | }
107 |
108 | // update configuration parameters
109 | function updateConfig() {
110 | // adjust config parameters
111 | config.maxValue = Math.max(config.maxValue, d3.max(data, function(d) {
112 | return d3.max(d.axes, function(o) { return o.value; });
113 | }));
114 | config.w *= config.levelScale;
115 | config.h *= config.levelScale;
116 | config.paddingX = config.w * config.levelScale;
117 | config.paddingY = config.h * config.levelScale;
118 |
119 | // if facet required:
120 | if (config.facet) {
121 | config.w /= data.length;
122 | config.h /= data.length;
123 | config.paddingX /= (data.length / config.facetPaddingScale);
124 | config.paddingY /= (data.length / config.facetPaddingScale);
125 | config.polygonPointSize *= Math.pow(0.9, data.length);
126 | }
127 | }
128 |
129 | //build visualization using the other build helper functions
130 | function buildVis(data) {
131 | buildVisComponents(data);
132 | buildCoordinates(data);
133 | if (config.showLevels) buildLevels();
134 | if (config.showLevelsLabels) buildLevelsLabels();
135 | if (config.showAxes) buildAxes();
136 | if (config.showAxesLabels) buildAxesLabels();
137 | if (config.showLegend) buildLegend(data);
138 | if (config.showVertices) buildVertices(data);
139 | if (config.showPolygons) buildPolygons(data);
140 | }
141 |
142 | // build main vis components
143 | function buildVisComponents(data) {
144 | // update vis parameters
145 | vis.allAxis = data[0].axes.map(function(i, j) { return i.axis; });
146 | vis.totalAxes = vis.allAxis.length;
147 | vis.radius = Math.min(config.w / 2, config.h / 2);
148 |
149 | // create main vis svg
150 | vis.svg = d3.select(id)
151 | .append("svg").classed("svg-vis", true)
152 | .attr("width", config.w + config.paddingX)
153 | .attr("height", config.h + config.paddingY)
154 | .append("svg:g")
155 | .attr("transform", "translate(" + config.translateX + "," + config.translateY + ")");;
156 |
157 | // create verticesTooltip
158 | d3.select("body")
159 | .append("div").classed("verticesTooltip", true)
160 | .attr("opacity", 0)
161 | .style({
162 | "position": "absolute",
163 | "color": "black",
164 | "font-size": "10px",
165 | "width": "100px",
166 | "height": "auto",
167 | "padding": "5px",
168 | "border": "2px solid gray",
169 | "border-radius": "5px",
170 | "pointer-events": "none",
171 | "opacity": "0",
172 | "background": "#f4f4f4"
173 | });
174 |
175 | vis.verticesTooltip = d3.select(".verticesTooltip")
176 |
177 | // create levels
178 | vis.levels = vis.svg.selectAll(".levels")
179 | .append("svg:g").classed("levels", true);
180 |
181 | // create axes
182 | vis.axes = vis.svg.selectAll(".axes")
183 | .append("svg:g").classed("axes", true);
184 |
185 | // create vertices
186 | vis.vertices = vis.svg.selectAll(".vertices");
187 |
188 | //Initiate Legend
189 | vis.legend = vis.svg.append("svg:g").classed("legend", true)
190 | .attr("height", config.h / 2)
191 | .attr("width", config.w / 2)
192 | .attr("transform", "translate(" + 0 + ", " + 1.1 * config.h + ")");
193 | }
194 |
195 | // builds out the levels of the spiderweb
196 | function buildLevels() {
197 | for (let level = 0; level < config.levels; level++) {
198 | let levelFactor = vis.radius * ((level + 1) / config.levels);
199 |
200 | // build level-lines
201 | vis.levels
202 | .data(vis.allAxis).enter()
203 | .append("svg:line").classed("level-lines", true)
204 | .attr("x1", function(d, i) { return levelFactor * (1 - Math.sin(i * config.radians / vis.totalAxes)); })
205 | .attr("y1", function(d, i) { return levelFactor * (1 - Math.cos(i * config.radians / vis.totalAxes)); })
206 | .attr("x2", function(d, i) { return levelFactor * (1 - Math.sin((i + 1) * config.radians / vis.totalAxes)); })
207 | .attr("y2", function(d, i) { return levelFactor * (1 - Math.cos((i + 1) * config.radians / vis.totalAxes)); })
208 | .attr("transform", "translate(" + (config.w / 2 - levelFactor) + ", " + (config.h / 2 - levelFactor) + ")")
209 | .attr("stroke", "gray")
210 | .attr("stroke-width", "0.5px");
211 | }
212 | }
213 |
214 | // builds out the levels labels
215 | function buildLevelsLabels() {
216 | for (let level = 0; level < config.levels; level++) {
217 | let levelFactor = vis.radius * ((level + 1) / config.levels);
218 |
219 | // build level-labels
220 | vis.levels
221 | .data([1]).enter()
222 | .append("svg:text").classed("level-labels", true)
223 | .text((config.maxValue * (level + 1) / config.levels).toFixed(2))
224 | .attr("x", function(d) { return levelFactor * (1 - Math.sin(0)); })
225 | .attr("y", function(d) { return levelFactor * (1 - Math.cos(0)); })
226 | .attr("transform", "translate(" + (config.w / 2 - levelFactor + 5) + ", " + (config.h / 2 - levelFactor) + ")")
227 | .attr("fill", "gray")
228 | .attr("font-family", "sans-serif")
229 | .attr("font-size", 10 * config.labelScale + "px");
230 | }
231 | }
232 |
233 | // builds out the axes
234 | function buildAxes() {
235 | vis.axes
236 | .data(vis.allAxis).enter()
237 | .append("svg:line").classed("axis-lines", true)
238 | .attr("x1", config.w / 2)
239 | .attr("y1", config.h / 2)
240 | .attr("x2", function(d, i) { return config.w / 2 * (1 - Math.sin(i * config.radians / vis.totalAxes)); })
241 | .attr("y2", function(d, i) { return config.h / 2 * (1 - Math.cos(i * config.radians / vis.totalAxes)); })
242 | .attr("stroke", "grey")
243 | .attr("stroke-width", "1px");
244 | }
245 |
246 | // builds out the axes labels
247 | function buildAxesLabels() {
248 | vis.axes
249 | .data(vis.allAxis).enter()
250 | .append("svg:text").classed("axis-labels", true)
251 | .text(function(d) { return d; })
252 | .attr("text-anchor", "middle")
253 | .attr("x", function(d, i) { return config.w / 2 * (1 - 1.3 * Math.sin(i * config.radians / vis.totalAxes)); })
254 | .attr("y", function(d, i) { return config.h / 2 * (1 - 1.1 * Math.cos(i * config.radians / vis.totalAxes)); })
255 | .attr("font-family", "sans-serif")
256 | .attr("font-size", 11 * config.labelScale + "px");
257 | }
258 |
259 | // builds [x, y] coordinates of polygon vertices.
260 | function buildCoordinates(data) {
261 | data.forEach(function(group) {
262 | group.axes.forEach(function(d, i) {
263 | d.coordinates = { // [x, y] coordinates
264 | x: config.w / 2 * (1 - (parseFloat(Math.max(d.value, 0)) / config.maxValue) * Math.sin(i * config.radians / vis.totalAxes)),
265 | y: config.h / 2 * (1 - (parseFloat(Math.max(d.value, 0)) / config.maxValue) * Math.cos(i * config.radians / vis.totalAxes))
266 | };
267 | });
268 | });
269 | }
270 |
271 | // builds out the polygon vertices of the dataset
272 | function buildVertices(data) {
273 | data.forEach(function(group, g) {
274 | vis.vertices
275 | .data(group.axes).enter()
276 | .append("svg:circle").classed("polygon-vertices", true)
277 | .attr("r", config.polygonPointSize)
278 | .attr("cx", function(d, i) { return d.coordinates.x; })
279 | .attr("cy", function(d, i) { return d.coordinates.y; })
280 | .attr("fill", config.colors(g))
281 | .on(over, verticesTooltipShow)
282 | .on(out, verticesTooltipHide);
283 | });
284 | }
285 |
286 | // builds out the polygon areas of the dataset
287 | function buildPolygons(data) {
288 | vis.vertices
289 | .data(data).enter()
290 | .append("svg:polygon").classed("polygon-areas", true)
291 | .attr("points", function(group) { // build verticesString for each group
292 | let verticesString = "";
293 | group.axes.forEach(function(d) { verticesString += d.coordinates.x + "," + d.coordinates.y + " "; });
294 | return verticesString;
295 | })
296 | .attr("stroke-width", "2px")
297 | .attr("stroke", function(d, i) { return config.colors(i); })
298 | .attr("fill", function(d, i) { return config.colors(i); })
299 | .attr("fill-opacity", config.polygonAreaOpacity)
300 | .attr("stroke-opacity", config.polygonStrokeOpacity)
301 | .on(over, function(d) {
302 | vis.svg.selectAll(".polygon-areas") // fade all other polygons out
303 | .transition(250)
304 | .attr("fill-opacity", 0.1)
305 | .attr("stroke-opacity", 0.1);
306 | d3.select(this) // focus on active polygon
307 | .transition(250)
308 | .attr("fill-opacity", 0.7)
309 | .attr("stroke-opacity", config.polygonStrokeOpacity);
310 | })
311 | .on(out, function() {
312 | d3.selectAll(".polygon-areas")
313 | .transition(250)
314 | .attr("fill-opacity", config.polygonAreaOpacity)
315 | .attr("stroke-opacity", 1);
316 | });
317 | }
318 |
319 | // builds out the legend
320 | function buildLegend(data) {
321 | //Create legend squares
322 | vis.legend.selectAll(".legend-tiles")
323 | .data(data).enter()
324 | .append("svg:rect").classed("legend-tiles", true)
325 | .attr("x", config.w - config.paddingX / 2)
326 | .attr("y", function(d, i) { return i * 2 * config.legendBoxSize; })
327 | .attr("width", config.legendBoxSize)
328 | .attr("height", config.legendBoxSize)
329 | .attr("fill", function(d, g) { return config.colors(g); });
330 |
331 | //Create text next to squares
332 | vis.legend.selectAll(".legend-labels")
333 | .data(data).enter()
334 | .append("svg:text").classed("legend-labels", true)
335 | .attr("x", config.w - config.paddingX / 2 + (1.5 * config.legendBoxSize))
336 | .attr("y", function(d, i) { return i * 2 * config.legendBoxSize; })
337 | .attr("dy", 0.07 * config.legendBoxSize + "em")
338 | .attr("font-size", 11 * config.labelScale + "px")
339 | .attr("fill", "gray")
340 | .text(function(d) {
341 | return d.group;
342 | });
343 | }
344 |
345 | // show tooltip of vertices
346 | function verticesTooltipShow(event) {
347 | vis.verticesTooltip.style("opacity", 0.9)
348 | .html(`Value: ${data.value}
349 | Description: ${data.description}
`)
350 | }
351 |
352 | // hide tooltip of vertices
353 | function verticesTooltipHide() {
354 | vis.verticesTooltip.style("opacity", 0);
355 | }
356 | }
357 | };
--------------------------------------------------------------------------------
/react-client/src/ScatterPlot.js:
--------------------------------------------------------------------------------
1 | import {
2 | Chart as ChartJS,
3 | LinearScale,
4 | PointElement,
5 | LineElement,
6 | Tooltip,
7 | Legend,
8 | } from 'chart.js';
9 | import { Scatter } from 'react-chartjs-2';
10 |
11 | ChartJS.register(LinearScale, PointElement, LineElement, Tooltip, Legend);
12 |
13 | export const options = {
14 | scales: {
15 | y: {
16 | beginAtZero: true,
17 | },
18 | },
19 | };
20 |
21 | export const data = {
22 | labels: [-56, 74, -25, 25, 68, -47, 1, -10, 38],
23 | datasets: [
24 | {
25 | label: 'This dataset',
26 | data: [6, 74, 25, -25, -68, 47, -1, 10, -38],
27 | backgroundColor: 'rgba(255, 99, 132, 1)',
28 | },
29 | ],
30 | };
31 |
32 | export default function ScatterPlot() {
33 | return ;
34 | }
35 |
--------------------------------------------------------------------------------
/react-client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import reportWebVitals from './reportWebVitals';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
13 | // If you want to start measuring performance in your app, pass a function
14 | // to log results (for example: reportWebVitals(console.log))
15 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
16 | reportWebVitals();
17 |
--------------------------------------------------------------------------------
/react-client/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------