diff --git a/package-lock.json b/package-lock.json
index dbade1e..7932e07 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1715,6 +1715,12 @@
"integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==",
"dev": true
},
+ "@types/lodash": {
+ "version": "4.14.159",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.159.tgz",
+ "integrity": "sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg==",
+ "dev": true
+ },
"@types/node": {
"version": "12.0.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.10.tgz",
@@ -8808,9 +8814,9 @@
}
},
"lodash": {
- "version": "4.17.15",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+ "version": "4.17.19",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+ "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
},
"lodash._reinterpolate": {
"version": "3.0.0",
diff --git a/package.json b/package.json
index 43bc154..969e7f2 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"gh-pages": "^2.0.0",
"html2canvas": "^1.0.0-alpha.12",
"jquery": "^3.3.1",
+ "lodash": "^4.17.19",
"lodash.debounce": "^4.0.8",
"node": "^11.12.0",
"query-string": "^6.6.0",
@@ -45,6 +46,7 @@
"homepage": "https://danielacorner.github.io/pave__react",
"devDependencies": {
"@types/jest": "^24.0.13",
+ "@types/lodash": "^4.14.159",
"@types/node": "^12.0.7",
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.4",
diff --git a/src/App.tsx b/src/App.tsx
index 32904d6..bddd9a2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,32 +1,40 @@
-import { createMuiTheme } from '@material-ui/core/styles';
+import { createMuiTheme } from "@material-ui/core/styles";
// import Navbar from './components/Navbar';
-import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider';
-import React, { useState } from 'react';
-import './App.css';
-import ContextProvider from './components/Context/ContextProvider';
-import AppWithContext from './AppWithContext';
-import { AddToHomeScreenBanner } from './components/AddToHomeScreenBanner';
-import { PictogramClipPathsDefs } from './components/Viz/PictogramClipPathsDefs';
-import { BeforeInstallPromptEvent } from './types';
+import MuiThemeProvider from "@material-ui/core/styles/MuiThemeProvider";
+import React, { useState } from "react";
+import "./App.css";
+import AppWithContext from "./AppWithContext";
+import { AddToHomeScreenBanner } from "./components/AddToHomeScreenBanner";
+import { PictogramClipPathsDefs } from "./components/Viz/PictogramClipPathsDefs";
+import { BeforeInstallPromptEvent } from "./types";
+import useStore from "./components/store";
+import { useMount } from "./utils/constants";
-export const brightGreen = '#49ac52';
+export const brightGreen = "#49ac52";
const theme = createMuiTheme({
palette: {
primary: {
main: brightGreen,
- contrastText: '#fff',
+ contrastText: "#fff",
},
- secondary: { main: '#64b5f6' },
+ secondary: { main: "#64b5f6" },
contrastThreshold: 3,
},
});
function App() {
const [deferredPrompt, setDeferredPrompt] = useState(
- null as null | BeforeInstallPromptEvent,
+ null as null | BeforeInstallPromptEvent
);
+ const initializeClusterCenters = useStore(
+ (state) => state.initializeClusterCenters
+ );
+
+ useMount(() => {
+ initializeClusterCenters();
+ });
- window.addEventListener('beforeinstallprompt', (event: any) => {
+ window.addEventListener("beforeinstallprompt", (event: any) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
event.preventDefault();
// Stash the event so it can be triggered later.
@@ -34,16 +42,14 @@ function App() {
});
return (
-
-
-
-
-
+
+
+
);
}
diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx
index bc1abfd..63a2003 100644
--- a/src/components/AppLayout.tsx
+++ b/src/components/AppLayout.tsx
@@ -1,11 +1,5 @@
// import queryString from 'query-string'
-import React, {
- useContext,
- useState,
- useEffect,
- useRef,
- useCallback,
-} from "react";
+import React, { useState, useEffect, useRef, useCallback } from "react";
import useMediaQuery from "@material-ui/core/useMediaQuery";
import styled from "styled-components/macro";
import {
@@ -15,15 +9,13 @@ import {
SKILLS_LOGI,
SKILLS_COMP,
SKILLS_MATH,
-} from "../utils/constants";
-import { ControlsContext } from "./Context/ContextProvider";
-import FiltersPanel from "./Controls/FiltersPanel";
-import SortPanel, {
STUDY,
SALARY,
STUDY_LABEL,
SALARY_LABEL,
-} from "./Controls/SortPanel";
+} from "../utils/constants";
+import FiltersPanel from "./Controls/FiltersPanel";
+import SortPanel from "./Controls/SortPanel";
import InfoDrawer from "./Viz/InfoDrawer";
import Tooltip from "./Viz/Tooltip";
import Viz from "./Viz/Viz";
@@ -33,6 +25,7 @@ import FORCE from "./FORCE";
import { useWindowSize } from "./useWindowSize";
import { NAV_HEIGHT } from "./Nav/Navbar";
import ContainerDimensions from "react-container-dimensions";
+import useStore from "./store";
const AppLayoutStyles = styled.div`
position: relative;
@@ -109,14 +102,16 @@ const AppLayout = () => {
x: { displayName: STUDY, dataLabel: STUDY_LABEL },
y: { displayName: SALARY, dataLabel: SALARY_LABEL },
});
- const { state, handleResize, restartSimulation } = useContext(
- ControlsContext
- );
+ const getRadiusScale = useStore((state) => state.getRadiusScale);
+ const zScale = useStore((state) => state.zScale);
+ const uniqueClusterValues = useStore((state) => state.uniqueClusterValues);
+ const sortedByValue = useStore((state) => state.sortedByValue);
+ const handleResize = useStore((state) => state.handleResize);
+ const restartSimulation = useStore((state) => state.restartSimulation);
const [isExpanded, setIsExpanded] = useState(
INITIAL_SUBSKILL_FILTERS_EXPANDED_STATE
);
- const { getRadiusScale, zScale, uniqueClusterValues, sortedByValue } = state;
const [isGraphView, setIsGraphView] = useState(false);
@@ -222,7 +217,7 @@ const AppLayout = () => {
});
startTooltipActive();
},
- [innerHeight, startTooltipActive]
+ [innerHeight]
);
const onClickNode = useCallback((event: Event, datum: any) => {
diff --git a/src/components/Context/ContextProvider.jsx b/src/components/Context/ContextProvider.jsx
index 48c4ee5..9361743 100644
--- a/src/components/Context/ContextProvider.jsx
+++ b/src/components/Context/ContextProvider.jsx
@@ -1,351 +1,352 @@
-import * as d3 from 'd3';
-import debounce from 'lodash.debounce';
-import React, { Component } from 'react';
-import NOCData from '../../assets/NOC-data';
-import FORCE from '../FORCE';
-import { INDUSTRY, WORKERS } from '../Controls/SortPanel';
-const NOCDataProcessed = NOCData.map(d => {
- d.name = d.job;
- return d;
-});
-
-export const ControlsContext = React.createContext();
-
-export const $ = element => document.querySelector(element); // jQuerify
-
-const getGraphContainerDims = () => {
- const graphContainer = $('#graphContainer');
- return graphContainer
- ? graphContainer.getBoundingClientRect()
- : { width: window.innerWidth, height: window.innerHeight * 0.8 };
-};
-
-// TODO: switch to hooks
-class ContextProvider extends Component {
- constructor(props) {
- super(props);
- this.state = {
- originalData: NOCDataProcessed,
- nodes: NOCDataProcessed,
- filters: {
- skillsLang: 0,
- skillsLogi: 0,
- skillsMath: 0,
- skillsComp: 0,
- s1DataAnalysis: 0,
- s2DecisionMaking: 0,
- s3FindingInformation: 0,
- s4JobTaskPlanningandOrganizing: 0,
- s5MeasurementandCalculation: 0,
- s6MoneyMath: 0,
- s7NumericalEstimation: 0,
- s8OralCommunication: 0,
- s9ProblemSolving: 0,
- s10Reading: 0,
- s11SchedulingorBudgetingandAccounting: 0,
- s12DigitalTechnology: 0,
- s13DocumentUse: 0,
- s14Writing: 0,
- s15CriticalThinking: 0,
- },
- radiusSelector: WORKERS,
- clusterSelector: INDUSTRY,
- getRadiusScale: () => {
- const radii = NOCData.map(d => d[this.state.radiusSelector]);
- const radiusRange = [5, 50];
- return d3
- .scaleSqrt() // square root scale because radius of a circle
- .domain([d3.min(radii), d3.max(radii)])
- .range(radiusRange);
- },
- uniqueClusterValues: NOCData.map(d => d[INDUSTRY]).filter(
- (value, index, self) => self.indexOf(value) === index,
- ),
- clusterCenters: [],
- sortedByValue: false,
- colouredByValue: false,
- svgBBox: 0,
- zScale: d3.scaleOrdinal(d3.schemeCategory10),
- isOffsetTop: false,
- offsetTop: 0,
- };
- }
-
- componentDidMount() {
- const {
- clusterCenters,
- radiusSelector,
- clusterSelector,
- uniqueClusterValues,
- } = this.state;
-
- // create clusters arrays
- NOCData.forEach(d => {
- const cluster = uniqueClusterValues.indexOf(d[clusterSelector]) + 1;
- // add to clusters array if it doesn't exist or the radius is larger than any other radius in the cluster
- if (
- !clusterCenters[cluster] ||
- d[radiusSelector] > clusterCenters[cluster][radiusSelector]
- ) {
- clusterCenters[cluster] = d;
- // todo: emit new cluster centers to/from context
- this.setState({ clusterCenters: clusterCenters });
- }
- });
-
- this.setState({
- zScale: d3.scaleOrdinal(d3.schemeCategory10),
- });
- window.addEventListener('resize', this.handleResize);
- setTimeout(this.handleResize, 1500);
-
- // tranlate nodes to center
-
- const { width, height } = getGraphContainerDims();
-
- const nodesG = $('#nodesG');
- if (nodesG) {
- nodesG.style.transform = `translate(${+width / 2}px,${+height / 2}px)`;
- }
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', this.handleResize);
- }
-
- // componentDidUpdate(nextProps, nextState) {}
-
- shouldComponentUpdate(nextProps, nextState) {
- return (
- this.state.nodes !== nextState.nodes ||
- this.state.sortedByValue !== nextState.sortedByValue
- );
- }
-
- getOffsetTop() {
- const graph = $('#graphContainer');
- const graphRect = graph && graph.getBoundingClientRect();
- const nodesG = $('#nodesG');
- const nodesRect = nodesG && nodesG.getBoundingClientRect();
-
- const newOffsetTop =
- this.state.sortedByValue && nodesRect.bottom > 0.975 * graphRect.bottom
- ? -(nodesRect.bottom - 0.975 * graphRect.bottom)
- : 0;
-
- this.setState({
- offsetTop: this.state.isOffsetTop ? this.state.offsetTop : newOffsetTop,
- isOffsetTop: this.state.isOffsetTop || newOffsetTop !== 0,
- });
-
- return this.state.isOffsetTop ? this.state.offsetTop : newOffsetTop;
- }
-
- getTranslate() {
- const { width, height } = getGraphContainerDims();
- return `${+width / 2}px,${+height / 2 + this.getOffsetTop()}px`;
- }
-
- getScale() {
- if (FORCE.isGraphView) {
- return 0.36;
- }
- // resize the graph container to fit the screen
- const { width, height } = getGraphContainerDims();
-
- // zoom in until you hit the edge of...
- const windowConstrainingLength = Math.min(width, height); // constrain by the smaller length
-
- const nodesG = $('#nodesG');
- const nodesBB = nodesG
- ? nodesG.getBBox()
- : { width: window.innerWidth - 10, height: window.innerHeight * 0.8 };
- // constrain the maximum nodes length
- const nodesConstrainedLength = Math.max(nodesBB.width, nodesBB.height);
-
- // bugfix: zooming in because initial nodesConstrainedLength = 100, and doesn't resize correctly when browser focus isn't on this tab
- if (
- nodesConstrainedLength === 100 &&
- this.state.nodes.length === this.state.originalData.length
- ) {
- return 0.95;
- }
-
- const scaleRatio = windowConstrainingLength / nodesConstrainedLength;
-
- return scaleRatio * 0.95; // zoom out a little extra
- }
-
- handleResize = debounce(() => {
- if (FORCE.isGraphView) {
- return;
- }
- const newScale = this.getScale();
- const graphContainer = $('#graphContainer');
- const svgBBox = graphContainer
- ? graphContainer.getBoundingClientRect()
- : { height: window.innerHeight * 0.8 };
- this.setState({
- svgBBox,
- });
-
- // resize size legend scale
- d3.selectAll('.sizeCircle')
- .style('transform', `scale(${newScale})`)
- .style('opacity', 1 / newScale);
- d3.selectAll('.size').style(
- 'padding-bottom',
- `${Math.min(Math.max(newScale - 1, 0) * 40, svgBBox.height / 5)}px`,
- );
-
- // scale up the spaces between the y axis ticks
- d3.select('.yAxis').style('transform', `scaleY(${newScale})`);
- d3.selectAll('.yAxis .tick').style('transform', `scaleY(${1 / newScale})`);
-
- // translate the nodes group into the middle and scale to fit
- const nodesG = $('#nodesG');
- if (nodesG) {
- nodesG.style.transform = `translate(${this.getTranslate()}) scale(${newScale})`;
- }
- }, 150);
-
- filteredNodes = () => {
- // filter the dataset according to the slider state
- const { filters, originalData } = this.state;
- const filterKeys = Object.keys(filters);
- const numFilters = filterKeys.length;
- const numNodes = originalData.length;
-
- const filtered = [];
- for (let i = 0; i < numNodes; i++) {
- const node = originalData[i];
- let keep = true;
- // for each filter variable
- for (let i = 0; i < numFilters; i++) {
- const filterVar = filterKeys[i]; // 'skillsLang', 'skillsMath'...
- // filter out the node if less than the slider value
- if (node[filterVar] < filters[filterVar]) {
- keep = false;
- }
- }
- keep && filtered.push(node);
- }
- return filtered;
- };
-
- filterNodes = () => {
- // pause the simulation if running
- !FORCE.paused && FORCE.stopSimulation();
-
- this.setState({
- nodes: this.filteredNodes(),
- });
- };
-
- restartSimulation = () => {
- this.setState({
- nodes: this.filteredNodes(),
- });
- const isStrongForce = this.state.sortedByValue && this.state.sortedType;
- const isMediumForce = this.state.sortedByValue;
- setTimeout(() => {
- FORCE.restartSimulation(this.state.nodes, isStrongForce, isMediumForce);
- }, 200);
- setTimeout(() => {
- this.handleResize();
- }, 1500);
- };
-
- handleFilterChange = (filter, value) => {
- this.setState(
- {
- filters: { ...this.state.filters, [filter]: value },
- },
- this.filterNodes,
- );
- };
- handleFilterMouseup = () => {
- this.setState({
- isOffsetTop: false, // recalculate offsetTop after each filter
- // offsetTop: 0,
- });
- if (FORCE.isGraphView) {
- return;
- }
- setTimeout(() => {
- // TODO: instead of actually moving the filters, could set the background fill instead?
- // set all filters to new minima on mouseup
- // let newMinima = {};
- // Object.keys(this.state.filters).forEach(filter => {
- // newMinima[filter] = Math.min(...this.state.nodes.map(d => d[filter]));
- // });
- // restart the simulation
- // this.setState({ filters: newMinima },
- this.restartSimulation();
- // );
- }, 0);
- };
- resetFilters = () => {
- const filtersReset = this.state.filters;
- Object.keys(this.state.filters).map(key => (filtersReset[key] = 0));
- this.setState(
- {
- filters: filtersReset,
- offsetTop: 0,
- isOffsetTop: false,
- },
- () => {
- this.filterNodes();
- this.restartSimulation();
- },
- );
- };
- sortByValue = (value, doSort = false) => {
- const { radiusSelector, getRadiusScale } = this.state;
- const newSortedValue = this.state.sortedByValue && !doSort ? false : value;
- this.setState({ sortedByValue: newSortedValue });
- FORCE.applySortForces({
- sortByValue: newSortedValue,
- getRadiusScale,
- radiusSelector,
- });
- setTimeout(this.handleResize, 2000);
- };
- setCurrentColor = value => {
- this.setState({ colouredByValue: value });
- };
- colourByValue = value => {
- if (!this.state.colouredByValue) {
- this.setState({ colouredByValue: value });
- FORCE.colourByValue({ doColour: true, value });
- } else {
- this.setState({ colouredByValue: false });
- FORCE.colourByValue({ doColour: false, value: null });
- }
- };
- render() {
- return (
- this.setState({ radiusSelector: x }),
- setClusterSelector: x => this.setState({ clusterSelector: x }),
- handleFilterChange: this.handleFilterChange,
- handleResize: this.handleResize,
- resetFilters: this.resetFilters,
- restartSimulation: this.restartSimulation,
- handleFilterMouseup: this.handleFilterMouseup,
- setNodes: nodes => this.setState({ nodes: nodes }),
- sortByValue: this.sortByValue,
- colourByValue: this.colourByValue,
- setCurrentColor: this.setCurrentColor,
- getScale: this.getScale,
- }}
- >
- {this.props.children}
-
- );
- }
-}
-
-export default ContextProvider;
+export {};
+// import * as d3 from 'd3';
+// import debounce from 'lodash.debounce';
+// import React, { Component } from 'react';
+// import NOCData from '../../assets/NOC-data';
+// import FORCE from '../FORCE';
+// import { INDUSTRY, WORKERS } from '../Controls/SortPanel';
+// const NOCDataProcessed = NOCData.map(d => {
+// d.name = d.job;
+// return d;
+// });
+
+// export const ControlsContext = React.createContext();
+
+// export const $ = element => document.querySelector(element); // jQuerify
+
+// const getGraphContainerDims = () => {
+// const graphContainer = $('#graphContainer');
+// return graphContainer
+// ? graphContainer.getBoundingClientRect()
+// : { width: window.innerWidth, height: window.innerHeight * 0.8 };
+// };
+
+// // TODO: switch to hooks
+// class ContextProvider extends Component {
+// constructor(props) {
+// super(props);
+// this.state = {
+// originalData: NOCDataProcessed,
+// nodes: NOCDataProcessed,
+// filters: {
+// skillsLang: 0,
+// skillsLogi: 0,
+// skillsMath: 0,
+// skillsComp: 0,
+// s1DataAnalysis: 0,
+// s2DecisionMaking: 0,
+// s3FindingInformation: 0,
+// s4JobTaskPlanningandOrganizing: 0,
+// s5MeasurementandCalculation: 0,
+// s6MoneyMath: 0,
+// s7NumericalEstimation: 0,
+// s8OralCommunication: 0,
+// s9ProblemSolving: 0,
+// s10Reading: 0,
+// s11SchedulingorBudgetingandAccounting: 0,
+// s12DigitalTechnology: 0,
+// s13DocumentUse: 0,
+// s14Writing: 0,
+// s15CriticalThinking: 0,
+// },
+// radiusSelector: WORKERS,
+// clusterSelector: INDUSTRY,
+// getRadiusScale: () => {
+// const radii = NOCData.map(d => d[this.state.radiusSelector]);
+// const radiusRange = [5, 50];
+// return d3
+// .scaleSqrt() // square root scale because radius of a circle
+// .domain([d3.min(radii), d3.max(radii)])
+// .range(radiusRange);
+// },
+// uniqueClusterValues: NOCData.map(d => d[INDUSTRY]).filter(
+// (value, index, self) => self.indexOf(value) === index,
+// ),
+// clusterCenters: [],
+// sortedByValue: false,
+// colouredByValue: false,
+// svgBBox: 0,
+// zScale: d3.scaleOrdinal(d3.schemeCategory10),
+// isOffsetTop: false,
+// offsetTop: 0,
+// };
+// }
+
+// componentDidMount() {
+// const {
+// clusterCenters,
+// radiusSelector,
+// clusterSelector,
+// uniqueClusterValues,
+// } = this.state;
+
+// // create clusters arrays
+// NOCData.forEach(d => {
+// const cluster = uniqueClusterValues.indexOf(d[clusterSelector]) + 1;
+// // add to clusters array if it doesn't exist or the radius is larger than any other radius in the cluster
+// if (
+// !clusterCenters[cluster] ||
+// d[radiusSelector] > clusterCenters[cluster][radiusSelector]
+// ) {
+// clusterCenters[cluster] = d;
+// // todo: emit new cluster centers to/from context
+// this.setState({ clusterCenters: clusterCenters });
+// }
+// });
+
+// this.setState({
+// zScale: d3.scaleOrdinal(d3.schemeCategory10),
+// });
+// window.addEventListener('resize', this.handleResize);
+// setTimeout(this.handleResize, 1500);
+
+// // tranlate nodes to center
+
+// const { width, height } = getGraphContainerDims();
+
+// const nodesG = $('#nodesG');
+// if (nodesG) {
+// nodesG.style.transform = `translate(${+width / 2}px,${+height / 2}px)`;
+// }
+// }
+
+// componentWillUnmount() {
+// window.removeEventListener('resize', this.handleResize);
+// }
+
+// // componentDidUpdate(nextProps, nextState) {}
+
+// shouldComponentUpdate(nextProps, nextState) {
+// return (
+// this.state.nodes !== nextState.nodes ||
+// this.state.sortedByValue !== nextState.sortedByValue
+// );
+// }
+
+// getOffsetTop() {
+// const graph = $('#graphContainer');
+// const graphRect = graph && graph.getBoundingClientRect();
+// const nodesG = $('#nodesG');
+// const nodesRect = nodesG && nodesG.getBoundingClientRect();
+
+// const newOffsetTop =
+// this.state.sortedByValue && nodesRect.bottom > 0.975 * graphRect.bottom
+// ? -(nodesRect.bottom - 0.975 * graphRect.bottom)
+// : 0;
+
+// this.setState({
+// offsetTop: this.state.isOffsetTop ? this.state.offsetTop : newOffsetTop,
+// isOffsetTop: this.state.isOffsetTop || newOffsetTop !== 0,
+// });
+
+// return this.state.isOffsetTop ? this.state.offsetTop : newOffsetTop;
+// }
+
+// getTranslate() {
+// const { width, height } = getGraphContainerDims();
+// return `${+width / 2}px,${+height / 2 + this.getOffsetTop()}px`;
+// }
+
+// getScale() {
+// if (FORCE.isGraphView) {
+// return 0.36;
+// }
+// // resize the graph container to fit the screen
+// const { width, height } = getGraphContainerDims();
+
+// // zoom in until you hit the edge of...
+// const windowConstrainingLength = Math.min(width, height); // constrain by the smaller length
+
+// const nodesG = $('#nodesG');
+// const nodesBB = nodesG
+// ? nodesG.getBBox()
+// : { width: window.innerWidth - 10, height: window.innerHeight * 0.8 };
+// // constrain the maximum nodes length
+// const nodesConstrainedLength = Math.max(nodesBB.width, nodesBB.height);
+
+// // bugfix: zooming in because initial nodesConstrainedLength = 100, and doesn't resize correctly when browser focus isn't on this tab
+// if (
+// nodesConstrainedLength === 100 &&
+// this.state.nodes.length === this.state.originalData.length
+// ) {
+// return 0.95;
+// }
+
+// const scaleRatio = windowConstrainingLength / nodesConstrainedLength;
+
+// return scaleRatio * 0.95; // zoom out a little extra
+// }
+
+// handleResize = debounce(() => {
+// if (FORCE.isGraphView) {
+// return;
+// }
+// const newScale = this.getScale();
+// const graphContainer = $('#graphContainer');
+// const svgBBox = graphContainer
+// ? graphContainer.getBoundingClientRect()
+// : { height: window.innerHeight * 0.8 };
+// this.setState({
+// svgBBox,
+// });
+
+// // resize size legend scale
+// d3.selectAll('.sizeCircle')
+// .style('transform', `scale(${newScale})`)
+// .style('opacity', 1 / newScale);
+// d3.selectAll('.size').style(
+// 'padding-bottom',
+// `${Math.min(Math.max(newScale - 1, 0) * 40, svgBBox.height / 5)}px`,
+// );
+
+// // scale up the spaces between the y axis ticks
+// d3.select('.yAxis').style('transform', `scaleY(${newScale})`);
+// d3.selectAll('.yAxis .tick').style('transform', `scaleY(${1 / newScale})`);
+
+// // translate the nodes group into the middle and scale to fit
+// const nodesG = $('#nodesG');
+// if (nodesG) {
+// nodesG.style.transform = `translate(${this.getTranslate()}) scale(${newScale})`;
+// }
+// }, 150);
+
+// filteredNodes = () => {
+// // filter the dataset according to the slider state
+// const { filters, originalData } = this.state;
+// const filterKeys = Object.keys(filters);
+// const numFilters = filterKeys.length;
+// const numNodes = originalData.length;
+
+// const filtered = [];
+// for (let i = 0; i < numNodes; i++) {
+// const node = originalData[i];
+// let keep = true;
+// // for each filter variable
+// for (let i = 0; i < numFilters; i++) {
+// const filterVar = filterKeys[i]; // 'skillsLang', 'skillsMath'...
+// // filter out the node if less than the slider value
+// if (node[filterVar] < filters[filterVar]) {
+// keep = false;
+// }
+// }
+// keep && filtered.push(node);
+// }
+// return filtered;
+// };
+
+// filterNodes = () => {
+// // pause the simulation if running
+// !FORCE.paused && FORCE.stopSimulation();
+
+// this.setState({
+// nodes: this.filteredNodes(),
+// });
+// };
+
+// restartSimulation = () => {
+// this.setState({
+// nodes: this.filteredNodes(),
+// });
+// const isStrongForce = this.state.sortedByValue && this.state.sortedType;
+// const isMediumForce = this.state.sortedByValue;
+// setTimeout(() => {
+// FORCE.restartSimulation(this.state.nodes, isStrongForce, isMediumForce);
+// }, 200);
+// setTimeout(() => {
+// this.handleResize();
+// }, 1500);
+// };
+
+// handleFilterChange = (filter, value) => {
+// this.setState(
+// {
+// filters: { ...this.state.filters, [filter]: value },
+// },
+// this.filterNodes,
+// );
+// };
+// handleFilterMouseup = () => {
+// this.setState({
+// isOffsetTop: false, // recalculate offsetTop after each filter
+// // offsetTop: 0,
+// });
+// if (FORCE.isGraphView) {
+// return;
+// }
+// setTimeout(() => {
+// // TODO: instead of actually moving the filters, could set the background fill instead?
+// // set all filters to new minima on mouseup
+// // let newMinima = {};
+// // Object.keys(this.state.filters).forEach(filter => {
+// // newMinima[filter] = Math.min(...this.state.nodes.map(d => d[filter]));
+// // });
+// // restart the simulation
+// // this.setState({ filters: newMinima },
+// this.restartSimulation();
+// // );
+// }, 0);
+// };
+// resetFilters = () => {
+// const filtersReset = this.state.filters;
+// Object.keys(this.state.filters).map(key => (filtersReset[key] = 0));
+// this.setState(
+// {
+// filters: filtersReset,
+// offsetTop: 0,
+// isOffsetTop: false,
+// },
+// () => {
+// this.filterNodes();
+// this.restartSimulation();
+// },
+// );
+// };
+// sortByValue = (value, doSort = false) => {
+// const { radiusSelector, getRadiusScale } = this.state;
+// const newSortedValue = this.state.sortedByValue && !doSort ? false : value;
+// this.setState({ sortedByValue: newSortedValue });
+// FORCE.applySortForces({
+// sortByValue: newSortedValue,
+// getRadiusScale,
+// radiusSelector,
+// });
+// setTimeout(this.handleResize, 2000);
+// };
+// setCurrentColor = value => {
+// this.setState({ colouredByValue: value });
+// };
+// colourByValue = value => {
+// if (!this.state.colouredByValue) {
+// this.setState({ colouredByValue: value });
+// FORCE.colourByValue({ doColour: true, value });
+// } else {
+// this.setState({ colouredByValue: false });
+// FORCE.colourByValue({ doColour: false, value: null });
+// }
+// };
+// render() {
+// return (
+// this.setState({ radiusSelector: x }),
+// setClusterSelector: x => this.setState({ clusterSelector: x }),
+// handleFilterChange: this.handleFilterChange,
+// handleResize: this.handleResize,
+// resetFilters: this.resetFilters,
+// restartSimulation: this.restartSimulation,
+// handleFilterMouseup: this.handleFilterMouseup,
+// setNodes: nodes => this.setState({ nodes: nodes }),
+// sortByValue: this.sortByValue,
+// colourByValue: this.colourByValue,
+// setCurrentColor: this.setCurrentColor,
+// getScale: this.getScale,
+// }}
+// >
+// {this.props.children}
+//
+// );
+// }
+// }
+
+// export default ContextProvider;
diff --git a/src/components/Controls/FilterSlider.jsx b/src/components/Controls/FilterSlider.jsx
index 54b1a93..1b0a723 100644
--- a/src/components/Controls/FilterSlider.jsx
+++ b/src/components/Controls/FilterSlider.jsx
@@ -1,10 +1,10 @@
-import { Collapse, IconButton } from '@material-ui/core';
-import Tooltip from '@material-ui/core/Tooltip';
-import Typography from '@material-ui/core/Typography';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
-import Slider from '@material-ui/lab/Slider';
-import React, { useContext, useState } from 'react';
-import styled from 'styled-components/macro';
+import { Collapse, IconButton } from "@material-ui/core";
+import Tooltip from "@material-ui/core/Tooltip";
+import Typography from "@material-ui/core/Typography";
+import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
+import Slider from "@material-ui/lab/Slider";
+import React, { useState } from "react";
+import styled from "styled-components/macro";
import {
FILTER_RANGE,
FILTER_TITLE,
@@ -12,8 +12,8 @@ import {
SLIDER_WIDTH_MD,
SUBSKILL_FILTER_TITLES,
SLIDER_TOOLTIP_TEXT,
-} from '../../utils/constants';
-import { ControlsContext } from '../Context/ContextProvider';
+} from "../../utils/constants";
+import useStore from "../store";
const LabelAndSliderStyles = styled.div`
background: white;
@@ -67,7 +67,7 @@ const LabelAndSliderStyles = styled.div`
`;
const MinMax = ({ visible, title }) => (
-
+
@@ -77,25 +77,26 @@ const MAX_SUBSKILL_VALUE = 10;
const SubskillFilters = ({ filterVar, onMouseUp }) => {
const subskillFilters = SUBSKILL_FILTER_TITLES(filterVar);
- const context = useContext(ControlsContext);
+ const filters = useStore((state) => state.filters);
+ const handleFilterChange = useStore((state) => state.handleFilterChange);
const [minMaxVisible, setMinMaxVisible] = useState(false);
return (
- {subskillFilters.map(subskill => (
+ {subskillFilters.map((subskill) => (
{subskill.title}
{
- context.handleFilterChange(subskill.dataLabel, value);
+ handleFilterChange(subskill.dataLabel, value);
}}
onMouseUp={onMouseUp}
onTouchEnd={onMouseUp}
@@ -136,10 +137,10 @@ const FilterSlider = ({
{FILTER_TITLE(filterVar)}
-
+
setIsExpanded({ ...isExpanded, [filterVar]: !filterIsExpanded })
diff --git a/src/components/Controls/FiltersPanel.jsx b/src/components/Controls/FiltersPanel.jsx
index fc37d51..ea95fe5 100644
--- a/src/components/Controls/FiltersPanel.jsx
+++ b/src/components/Controls/FiltersPanel.jsx
@@ -1,8 +1,8 @@
-import React, { useContext } from 'react';
-import styled from 'styled-components/macro';
-import { SLIDER_WIDTH_LG, SLIDER_WIDTH_MD } from '../../utils/constants';
-import { ControlsContext } from '../Context/ContextProvider';
-import FilterSlider from './FilterSlider';
+import React from "react";
+import styled from "styled-components/macro";
+import { SLIDER_WIDTH_LG, SLIDER_WIDTH_MD } from "../../utils/constants";
+import FilterSlider from "./FilterSlider";
+import useStore from "../store";
const FiltersPanelStyles = styled.div`
margin: 10px 0px 0px 6px;
@@ -35,21 +35,21 @@ const FiltersPanelStyles = styled.div`
`;
const FiltersPanel = ({ filterVariables, isExpanded, setIsExpanded }) => {
- const { handleFilterMouseup, handleFilterChange, state } = useContext(
- ControlsContext,
- );
+ const handleFilterMouseup = useStore((state) => state.handleFilterMouseup);
+ const handleFilterChange = useStore((state) => state.handleFilterChange);
+ const filters = useStore((state) => state.filters);
return (
- {filterVariables.map(filterVar => (
+ {filterVariables.map((filterVar) => (
{
+ value={filters[filterVar]}
+ onChange={(value) => {
handleFilterChange(filterVar, value);
}}
onMouseUp={handleFilterMouseup}
diff --git a/src/components/Controls/GraphViewButton.tsx b/src/components/Controls/GraphViewButton.tsx
index c7795f1..d41437f 100644
--- a/src/components/Controls/GraphViewButton.tsx
+++ b/src/components/Controls/GraphViewButton.tsx
@@ -1,22 +1,28 @@
-import React from 'react';
-import { Switch } from '@material-ui/core';
-import styled from 'styled-components/macro';
-import FormControlLabel from '@material-ui/core/FormControlLabel';
-import Tooltip from '@material-ui/core/Tooltip';
-import { MenuItem, Select } from '@material-ui/core';
-import { AUTOMATION_RISK, WORKERS, SALARY, STUDY, INDUSTRY } from './SortPanel';
-import { getDatalabelMap } from '../../utils/constants';
+import React from "react";
+import { Switch } from "@material-ui/core";
+import styled from "styled-components/macro";
+import FormControlLabel from "@material-ui/core/FormControlLabel";
+import Tooltip from "@material-ui/core/Tooltip";
+import { MenuItem, Select } from "@material-ui/core";
+import {
+ AUTOMATION_RISK,
+ WORKERS,
+ SALARY,
+ STUDY,
+ INDUSTRY,
+ getDatalabelMap,
+} from "../../utils/constants";
export const VariablePickerMenu = ({ value, onChange, isIndustry = false }) => (