Skip to content

Commit

Permalink
Move ColorScale and Map component in lib
Browse files Browse the repository at this point in the history
- Convert the following components to typescript:
    - Map/index.tsx
    - Map/OlMap/index.tsx
    - Map/HeatmapLayer/index.tsx
    - ColorScale/index.tsx
- NOTE: Both lib and storybook are broken
- Add dependencies for Map component
- Remove eslint check for require-default-props
  • Loading branch information
tnagorra authored and frozenhelium committed Mar 7, 2024
1 parent e1c0fdb commit 6fb07ec
Show file tree
Hide file tree
Showing 55 changed files with 3,891 additions and 3,264 deletions.
2 changes: 1 addition & 1 deletion lib/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ module.exports = {
'react/jsx-indent': [2, 4],
'react/jsx-indent-props': [2, 4],
'react/jsx-filename-extension': ['error', { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],

'react/require-default-props': ['warn', { ignoreFunctionalComponents: true }],
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
Expand Down
12 changes: 11 additions & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,16 @@
"@babel/runtime-corejs3": "^7.22.6",
"@knight-lab/timelinejs": "^3.9.3",
"@togglecorp/fujs": "^2.1.1",
"react-icons": "^5.0.1"
"d3": "^7.8.5",
"d3-scale": "^4.0.2",
"d3-scale-chromatic": "^3.0.0",
"file-saver": "^2.0.5",
"html-to-image": "^1.11.11",
"ol": "^7.3.0",
"ol-ext": "^4.0.10",
"react-icons": "^5.0.1",
"statsbreaks": "^1.0.4",
"webfontloader": "^1.6.28"
},
"peerDependencies": {
"react": "^18.2.0",
Expand All @@ -56,6 +65,7 @@
"@rollup/plugin-eslint": "^9.0.4",
"@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-node-resolve": "^15.1.0",
"@types/geojson": "^7946.0.14",
"@types/jest": "^29.5.2",
"@types/node": "^20.4.1",
"@types/react": "^18.2.14",
Expand Down
104 changes: 104 additions & 0 deletions lib/src/components/ColorScale/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { scalePow } from 'd3';
import { _cs } from '@togglecorp/fujs';
import * as d3ColorScale from 'd3-scale-chromatic';

import styles from './styles.module.css';

// FIXME: We have removed the default behavior for colorScale and colorScaleType
export type Props = {
steps?: number;
pow?: number;
containerClass?: string; // FIXME: why are we using this style
inverted?: boolean;
} & ({
colorScale: 'BrBG' | 'PRGn' | 'PiYG' | 'PuOr' | 'RdBu' | 'RdGy' | 'RdYlBu' | 'RdYlGn' | 'Spectral' | 'Blues' | 'Greens' | 'Greys' | 'Oranges' | 'Purples' | 'Reds' | 'BuPu' | 'GnBu' | 'OrRd' | 'PuBuGn' | 'PuBu' | 'PuRd' | 'RdPu' | 'YlGnBu' | 'YlGn' | 'YlOrBr' | 'YlOrRd';
colorScaleType: 'continuous' | 'steps';
} | {
colorScale: 'Category10' | 'Accent' | 'Dark2' | 'Paired' | 'Pastel1' | 'Pastel2' | 'Set1' | 'Set2' | 'Set3' | 'Tableau10';
colorScaleType: 'categorised';
})

function ColorScale(props: Props) {
const {
steps = 12,
pow = 1,
containerClass = 'colorScaleDiv',
inverted = false,
} = props;

let numSteps = steps;
// eslint-disable-next-line react/destructuring-assignment
if (props.colorScaleType === 'continuous') {
numSteps = 50;
}

let colorsArray: readonly string[];
// eslint-disable-next-line react/destructuring-assignment
if (props.colorScaleType === 'categorised') {
// eslint-disable-next-line react/destructuring-assignment
colorsArray = d3ColorScale[`scheme${props.colorScale}`];
} else {
// eslint-disable-next-line react/destructuring-assignment
const interpolator = d3ColorScale[`interpolate${props.colorScale}`];
colorsArray = Array.from(
{ length: numSteps },
(_, i) => interpolator(i * (1 / (numSteps))),
);
}

let fillPow = pow;
// eslint-disable-next-line react/destructuring-assignment
if (props.colorScaleType === 'steps' || props.colorScaleType === 'categorised') {
fillPow = 1;
}

if (inverted) {
colorsArray = [...colorsArray].reverse();
}

const colorsArrayPow = scalePow()
.exponent(fillPow)
.domain([0, numSteps]);

const colorStrPow = colorsArray.map((_, i) => {
const colorIndex = Math.ceil(colorsArrayPow(i) * (numSteps));
return colorsArray[colorIndex];
});

const colorsString = colorStrPow.join(', ');
const cssGradient = {
background: `linear-gradient(90deg, ${colorsString})`,
};

return (
<div
// FIXME: use _cs
className={_cs(styles.colorScaleContainer, styles[containerClass])}
>
{/* eslint-disable-next-line react/destructuring-assignment */}
{(props.colorScaleType === 'steps' || props.colorScaleType === 'categorised') && (
<div className={styles.colorScale}>
{colorStrPow.map((color, index) => (
<div
key={`${index + color}`}
className={styles.steps}
>
<div style={{ backgroundColor: color }} />
</div>
))}
</div>
)}
{/* eslint-disable-next-line react/destructuring-assignment */}
{props.colorScaleType === 'continuous' && (
<div
className={styles.colorScale}
style={cssGradient}
role="presentation"
/>
)}
</div>
);
}

export default ColorScale;
172 changes: 172 additions & 0 deletions lib/src/components/Map/Layers/HeatmapLayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { useEffect, useState } from 'react';
import { Map as MapFromLib } from 'ol';
import { Heatmap } from 'ol/layer';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import { fromLonLat } from 'ol/proj';
import * as d3ColorScale from 'd3-scale-chromatic';
import { scaleLog } from 'd3-scale';

import { HeatMapLayer, HeatMapLayerProperty } from '../index';
import { vector } from '../helpers';

interface Props extends Pick<HeatMapLayer, 'data' | 'zIndex' | 'opacity' | 'blur' | 'radius' | 'fillPalette' | 'weighted' | 'scaleDataMax'>
{
map: MapFromLib | undefined;
}

function HeatmapLayer(props: Props) {
const {
map,
data,
zIndex = 1,
opacity = 1,
blur,
radius,
fillPalette,
weighted = false,
scaleDataMax = 350,
} = props;

const [heatmapLayer, setHeatmapLayer] = useState<Heatmap | undefined>(undefined);

const scaleWeight = scaleLog()
.domain([1, scaleDataMax])
.range([0.4, 1]);

useEffect(
() => {
if (!map) {
return undefined;
}

let properties: HeatMapLayerProperty[];
if (Array.isArray(data)) {
properties = data;
} else {
properties = data.features.map((datum) => ({
...datum.properties,
lon: datum.geometry.coordinates[0],
lat: datum.geometry.coordinates[1],
}));
}

const features = properties.map((item) => {
const feature = new Feature(
new Point(fromLonLat([item.lon, item.lat])),
);
feature.setProperties(item);
return feature;
});

if (map && heatmapLayer) {
map.removeLayer(heatmapLayer);
}

const interpolator = d3ColorScale[`interpolate${fillPalette}`];
const numSteps = 5;
// eslint-disable-next-line
const colors = Array.from({ length: numSteps }, (_, i) => interpolator(i * (1 / numSteps)));

const vectorLayer = new Heatmap({
source: vector<Point>({ features }),
blur,
radius,
gradient: colors,
weight: (feature) => {
if (weighted) {
const w = scaleWeight(parseFloat(feature.get('fatalities'))) || 0;
return w;
}
return 0.7;
},
});
map.addLayer(vectorLayer);
setHeatmapLayer(vectorLayer);

return () => {
if (map) {
map.removeLayer(vectorLayer);
}
};
},
// FIXME: for the missing dependencies, we can store them in intial
// values using useState or useRef
// For reference, check other libraries like toggle-form or re-map
[map, fillPalette, weighted],

Check warning on line 96 in lib/src/components/Map/Layers/HeatmapLayer.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has missing dependencies: 'blur', 'data', 'heatmapLayer', 'radius', and 'scaleWeight'. Either include them or remove the dependency array
);

useEffect(
() => {
if (!heatmapLayer) {
return;
}

let properties: HeatMapLayerProperty[];
if (Array.isArray(data)) {
properties = data;
} else {
properties = data.features.map((datum) => ({
...datum.properties,
lon: datum.geometry.coordinates[0],
lat: datum.geometry.coordinates[1],
exclude_from_heatmap: Boolean(datum.properties?.exclude_from_heatmap),
}));
}

const features = properties.filter((item) => !item.exclude_from_heatmap).map((item) => {
const feature = new Feature(
new Point(fromLonLat([item.lon, item.lat])),
);
feature.setProperties(item);
return feature;
});

// FIXME: should we return instead
const source = heatmapLayer.getSource();
source?.clear();
source?.addFeatures(features);
},
// FIXME: We might not need to use JSON.stringify
[heatmapLayer, JSON.stringify(data)],

Check warning on line 131 in lib/src/components/Map/Layers/HeatmapLayer.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has a missing dependency: 'data'. Either include it or remove the dependency array

Check warning on line 131 in lib/src/components/Map/Layers/HeatmapLayer.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked
);

useEffect(
() => {
if (!heatmapLayer) {
return;
}
heatmapLayer.setOpacity(opacity);
},
[heatmapLayer, opacity],
);

useEffect(
() => {
if (!heatmapLayer) {
return;
}
heatmapLayer.setZIndex(zIndex);
},
[heatmapLayer, zIndex],
);

useEffect(() => {
if (!heatmapLayer) return;
heatmapLayer.setBlur(blur);
}, [heatmapLayer, blur]);

useEffect(
() => {
if (!heatmapLayer) {
return;
}
heatmapLayer.setRadius(radius);
},
[heatmapLayer, radius],
);

return null;
}

export default HeatmapLayer;
Loading

0 comments on commit 6fb07ec

Please sign in to comment.