bento-charts 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -23
- package/dist/ChartConfigProvider.d.ts +4 -2
- package/dist/ChartConfigProvider.js +6 -1
- package/dist/ChartConfigProvider.js.map +1 -1
- package/dist/Components/Charts/BentoBarChart.js.map +1 -1
- package/dist/Components/Charts/BentoPie.d.ts +1 -1
- package/dist/Components/Charts/BentoPie.js +39 -37
- package/dist/Components/Charts/BentoPie.js.map +1 -1
- package/dist/Components/Maps/BentoChoroplethMap.d.ts +3 -0
- package/dist/Components/Maps/BentoChoroplethMap.js +90 -0
- package/dist/Components/Maps/BentoChoroplethMap.js.map +1 -0
- package/dist/Components/Maps/BentoMapContainer.d.ts +7 -0
- package/dist/Components/Maps/BentoMapContainer.js +33 -0
- package/dist/Components/Maps/BentoMapContainer.js.map +1 -0
- package/dist/Components/Maps/BentoOSMTileLayer.d.ts +2 -0
- package/dist/Components/Maps/BentoOSMTileLayer.js +6 -0
- package/dist/Components/Maps/BentoOSMTileLayer.js.map +1 -0
- package/dist/Components/Maps/BentoPointMap.d.ts +3 -0
- package/dist/Components/Maps/BentoPointMap.js +25 -0
- package/dist/Components/Maps/BentoPointMap.js.map +1 -0
- package/dist/Components/Maps/controls/MapLegendContinuous.d.ts +10 -0
- package/dist/Components/Maps/controls/MapLegendContinuous.js +19 -0
- package/dist/Components/Maps/controls/MapLegendContinuous.js.map +1 -0
- package/dist/Components/Maps/controls/MapLegendDiscrete.d.ts +8 -0
- package/dist/Components/Maps/controls/MapLegendDiscrete.js +22 -0
- package/dist/Components/Maps/controls/MapLegendDiscrete.js.map +1 -0
- package/dist/Components/Maps/controls/utils.d.ts +4 -0
- package/dist/Components/Maps/controls/utils.js +7 -0
- package/dist/Components/Maps/controls/utils.js.map +1 -0
- package/dist/constants/chartConstants.d.ts +0 -1
- package/dist/constants/chartConstants.js +0 -1
- package/dist/constants/chartConstants.js.map +1 -1
- package/dist/constants/mapConstants.d.ts +2 -0
- package/dist/constants/mapConstants.js +3 -0
- package/dist/constants/mapConstants.js.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/maps.d.ts +3 -0
- package/dist/maps.js +5 -0
- package/dist/maps.js.map +1 -0
- package/dist/types/chartTypes.d.ts +12 -9
- package/dist/types/geoJSONTypes.d.ts +18 -0
- package/dist/types/geoJSONTypes.js +2 -0
- package/dist/types/geoJSONTypes.js.map +1 -0
- package/dist/types/mapTypes.d.ts +43 -0
- package/dist/types/mapTypes.js +2 -0
- package/dist/types/mapTypes.js.map +1 -0
- package/package.json +30 -4
- package/src/ChartConfigProvider.tsx +17 -2
- package/src/Components/Charts/BentoBarChart.tsx +2 -2
- package/src/Components/Charts/BentoPie.tsx +64 -57
- package/src/Components/Maps/BentoChoroplethMap.tsx +137 -0
- package/src/Components/Maps/BentoMapContainer.tsx +35 -0
- package/src/Components/Maps/BentoOSMTileLayer.tsx +7 -0
- package/src/Components/Maps/BentoPointMap.tsx +30 -0
- package/src/Components/Maps/controls/MapLegendContinuous.tsx +32 -0
- package/src/Components/Maps/controls/MapLegendDiscrete.tsx +31 -0
- package/src/Components/Maps/controls/utils.ts +8 -0
- package/src/constants/chartConstants.ts +0 -1
- package/src/constants/mapConstants.ts +4 -0
- package/src/index.ts +7 -0
- package/src/maps.ts +4 -0
- package/src/react-app-env.d.ts +1 -0
- package/src/styles.css +48 -0
- package/src/types/chartTypes.ts +13 -9
- package/src/types/geoJSONTypes.ts +21 -0
- package/src/types/mapTypes.ts +52 -0
- package/webpack.config.js +60 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React, { Ref, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { GeoJSON, Popup } from 'react-leaflet';
|
|
3
|
+
import { interpolateRgb } from 'd3-interpolate';
|
|
4
|
+
import type { Feature as GeoJSONFeatureType } from 'geojson';
|
|
5
|
+
import type {
|
|
6
|
+
ControlPosition,
|
|
7
|
+
GeoJSON as LeafletGeoJSON,
|
|
8
|
+
LeafletMouseEvent,
|
|
9
|
+
LeafletEventHandlerFnMap,
|
|
10
|
+
PathOptions,
|
|
11
|
+
} from 'leaflet';
|
|
12
|
+
|
|
13
|
+
import type { ChoroplethMapProps } from '../../types/mapTypes';
|
|
14
|
+
|
|
15
|
+
import BentoMapContainer from './BentoMapContainer';
|
|
16
|
+
import MapLegendContinuous from './controls/MapLegendContinuous';
|
|
17
|
+
import MapLegendDiscrete from './controls/MapLegendDiscrete';
|
|
18
|
+
|
|
19
|
+
const DEFAULT_CATEGORY = '';
|
|
20
|
+
const POS_BOTTOM_RIGHT: ControlPosition = 'bottomright';
|
|
21
|
+
|
|
22
|
+
const BentoChoroplethMap = ({
|
|
23
|
+
height,
|
|
24
|
+
data: originalData,
|
|
25
|
+
preFilter,
|
|
26
|
+
dataMap,
|
|
27
|
+
postFilter,
|
|
28
|
+
center,
|
|
29
|
+
zoom,
|
|
30
|
+
tileLayer,
|
|
31
|
+
colorMode,
|
|
32
|
+
features,
|
|
33
|
+
categoryProp,
|
|
34
|
+
onClick,
|
|
35
|
+
renderPopupBody,
|
|
36
|
+
}: ChoroplethMapProps) => {
|
|
37
|
+
const data = useMemo(() => {
|
|
38
|
+
let data = [...originalData];
|
|
39
|
+
if (preFilter) data = data.filter(preFilter);
|
|
40
|
+
if (dataMap) data = data.map(dataMap);
|
|
41
|
+
if (postFilter) data = data.filter(postFilter);
|
|
42
|
+
return data;
|
|
43
|
+
}, [originalData]);
|
|
44
|
+
|
|
45
|
+
const dataByFeatureCat = useMemo(() => Object.fromEntries(data.map((d) => [d.x, d.y])), [data]);
|
|
46
|
+
|
|
47
|
+
const minYVal = useMemo(() => Math.min(...data.map((d) => d.y)), [data]);
|
|
48
|
+
const maxYVal = useMemo(() => Math.max(...data.map((d) => d.y)), [data]);
|
|
49
|
+
|
|
50
|
+
const calculateColor = useCallback(
|
|
51
|
+
(v: number | undefined): string =>
|
|
52
|
+
colorMode.mode === 'continuous'
|
|
53
|
+
? interpolateRgb(colorMode.minColor, colorMode.maxColor)(((v ?? minYVal) - minYVal) / (maxYVal - minYVal))
|
|
54
|
+
: colorMode.colorFunction(v),
|
|
55
|
+
[colorMode, minYVal, maxYVal]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const shapeStyle = useCallback(
|
|
59
|
+
(f: GeoJSONFeatureType): PathOptions => {
|
|
60
|
+
const fProps = f.properties ?? {};
|
|
61
|
+
if (!Object.keys(fProps).includes(categoryProp)) {
|
|
62
|
+
console.warn(`Feature is missing category prop ${categoryProp}`, f);
|
|
63
|
+
}
|
|
64
|
+
const cat: string = fProps[categoryProp] ?? DEFAULT_CATEGORY;
|
|
65
|
+
return {
|
|
66
|
+
color: 'white',
|
|
67
|
+
weight: 2,
|
|
68
|
+
fillColor: calculateColor(dataByFeatureCat[cat]),
|
|
69
|
+
fillOpacity: 1, // actual opacity set by fillColor
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
[data, features]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const [popupContents, setPopupContents] = useState<React.ReactNode | null>(null);
|
|
76
|
+
|
|
77
|
+
const eventHandlers = useMemo(
|
|
78
|
+
() =>
|
|
79
|
+
({
|
|
80
|
+
click: (e: LeafletMouseEvent) => {
|
|
81
|
+
const feature = e.sourceTarget.feature as GeoJSONFeatureType;
|
|
82
|
+
const fProps = feature.properties ?? {};
|
|
83
|
+
const title = fProps.title ? `${fProps.title} (${fProps[categoryProp]})` : fProps[categoryProp];
|
|
84
|
+
setPopupContents(
|
|
85
|
+
<div>
|
|
86
|
+
<h4 style={{ marginBottom: renderPopupBody ? 6 : 0 }}>
|
|
87
|
+
{onClick ? (
|
|
88
|
+
<a
|
|
89
|
+
href="#"
|
|
90
|
+
onClick={() => {
|
|
91
|
+
if (onClick) onClick(feature);
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
{title}
|
|
95
|
+
</a>
|
|
96
|
+
) : (
|
|
97
|
+
<span>{title}</span>
|
|
98
|
+
)}
|
|
99
|
+
</h4>
|
|
100
|
+
{renderPopupBody ? renderPopupBody(feature, dataByFeatureCat[fProps[categoryProp]]) : null}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
} as LeafletEventHandlerFnMap),
|
|
105
|
+
[onClick, categoryProp, renderPopupBody]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const geoJsonLayer: Ref<LeafletGeoJSON> = useRef(null);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
// Bizarre workaround needed for react-leaflet when handling `features` change:
|
|
111
|
+
// See https://github.com/PaulLeCam/react-leaflet/issues/332#issuecomment-731379795
|
|
112
|
+
if (geoJsonLayer.current) {
|
|
113
|
+
geoJsonLayer.current.clearLayers().addData(features);
|
|
114
|
+
}
|
|
115
|
+
}, [features]);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<BentoMapContainer height={height} center={center} zoom={zoom} tileLayer={tileLayer}>
|
|
119
|
+
<GeoJSON ref={geoJsonLayer} data={features} style={shapeStyle} eventHandlers={eventHandlers}>
|
|
120
|
+
<Popup>{popupContents}</Popup>
|
|
121
|
+
</GeoJSON>
|
|
122
|
+
{colorMode.mode === 'continuous' ? (
|
|
123
|
+
<MapLegendContinuous
|
|
124
|
+
position={POS_BOTTOM_RIGHT}
|
|
125
|
+
minColor={colorMode.minColor}
|
|
126
|
+
minValue={minYVal}
|
|
127
|
+
maxColor={colorMode.maxColor}
|
|
128
|
+
maxValue={maxYVal}
|
|
129
|
+
/>
|
|
130
|
+
) : (
|
|
131
|
+
<MapLegendDiscrete position={POS_BOTTOM_RIGHT} legendItems={colorMode.legendItems} />
|
|
132
|
+
)}
|
|
133
|
+
</BentoMapContainer>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export default BentoChoroplethMap;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import { MapContainer } from 'react-leaflet';
|
|
4
|
+
import L, { Point } from 'leaflet';
|
|
5
|
+
|
|
6
|
+
import BentoOSMTileLayer from './BentoOSMTileLayer';
|
|
7
|
+
import type { BaseMapProps } from '../../types/mapTypes';
|
|
8
|
+
|
|
9
|
+
import iconPng from 'leaflet/dist/images/marker-icon.png';
|
|
10
|
+
import icon2XPng from 'leaflet/dist/images/marker-icon-2x.png';
|
|
11
|
+
import iconShadowPng from 'leaflet/dist/images/marker-shadow.png';
|
|
12
|
+
|
|
13
|
+
const defaultIcon = L.icon({
|
|
14
|
+
iconUrl: iconPng,
|
|
15
|
+
iconRetinaUrl: icon2XPng,
|
|
16
|
+
iconSize: new Point(25, 41),
|
|
17
|
+
iconAnchor: new Point(12, 41),
|
|
18
|
+
popupAnchor: new Point(1, -41),
|
|
19
|
+
shadowUrl: iconShadowPng,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
L.Marker.prototype.options.icon = defaultIcon;
|
|
23
|
+
|
|
24
|
+
interface MapContainerProps extends BaseMapProps {
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const BentoMapContainer = ({ height, center, zoom, children, tileLayer }: MapContainerProps) => (
|
|
29
|
+
<MapContainer style={{ height }} center={center} zoom={zoom}>
|
|
30
|
+
{tileLayer ?? <BentoOSMTileLayer />}
|
|
31
|
+
{children}
|
|
32
|
+
</MapContainer>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export default BentoMapContainer;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TileLayer } from 'react-leaflet';
|
|
3
|
+
import { OSM_TILE_LAYER_ATTRIBUTION, OSM_TILE_LAYER_TEMPLATE } from '../../constants/mapConstants';
|
|
4
|
+
|
|
5
|
+
const BentoOSMTileLayer = () => <TileLayer attribution={OSM_TILE_LAYER_ATTRIBUTION} url={OSM_TILE_LAYER_TEMPLATE} />;
|
|
6
|
+
|
|
7
|
+
export default BentoOSMTileLayer;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Marker, Popup } from 'react-leaflet';
|
|
3
|
+
import BentoMapContainer from './BentoMapContainer';
|
|
4
|
+
import type { PointMapProps } from '../../types/mapTypes';
|
|
5
|
+
|
|
6
|
+
const BentoPointMap = ({ height, center, zoom, tileLayer, data, onClick, renderPopupBody }: PointMapProps) => {
|
|
7
|
+
return (
|
|
8
|
+
<BentoMapContainer height={height} center={center} zoom={zoom} tileLayer={tileLayer}>
|
|
9
|
+
{data.map((point, i) => {
|
|
10
|
+
const { coordinates, title } = point;
|
|
11
|
+
|
|
12
|
+
// We expect points in [long, lat] order (consistent with GeoJSON), but Leaflet wants them in [lat, long].
|
|
13
|
+
const coordinatesLatLongOrder: [number, number] = [coordinates[1], coordinates[0]];
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Marker key={i} position={coordinatesLatLongOrder}>
|
|
17
|
+
<Popup>
|
|
18
|
+
<h4 style={{ marginBottom: renderPopupBody ? 6 : 0 }}>
|
|
19
|
+
{onClick ? <a onClick={() => onClick(point)}>{title}</a> : <>{title}</>}
|
|
20
|
+
</h4>
|
|
21
|
+
{renderPopupBody ? renderPopupBody(point) : null}
|
|
22
|
+
</Popup>
|
|
23
|
+
</Marker>
|
|
24
|
+
);
|
|
25
|
+
})}
|
|
26
|
+
</BentoMapContainer>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default BentoPointMap;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ControlPosition } from 'leaflet';
|
|
3
|
+
import { controlPositionClasses } from './utils';
|
|
4
|
+
|
|
5
|
+
export interface MapLegendDiscreteProps {
|
|
6
|
+
position: ControlPosition;
|
|
7
|
+
minValue: number;
|
|
8
|
+
minColor: string;
|
|
9
|
+
maxValue: number;
|
|
10
|
+
maxColor: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const MapLegendContinuous = ({ position, minValue, minColor, maxValue, maxColor }: MapLegendDiscreteProps) => {
|
|
14
|
+
return (
|
|
15
|
+
<div className={controlPositionClasses[position]}>
|
|
16
|
+
<div className="leaflet-control bento-charts--map--legend">
|
|
17
|
+
<div className="bento-charts--map--legend--scale">
|
|
18
|
+
<div
|
|
19
|
+
className="bento-charts--continuous-scale"
|
|
20
|
+
style={{ background: `linear-gradient(0deg, ${minColor} 0%, ${maxColor} 100%)` }}
|
|
21
|
+
/>
|
|
22
|
+
<div className="bento-charts--map--legend--values">
|
|
23
|
+
<span>{maxValue}</span>
|
|
24
|
+
<span>{minValue}</span>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default MapLegendContinuous;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ControlPosition } from 'leaflet';
|
|
3
|
+
import type { MapDiscreteLegendItem } from '../../../types/mapTypes';
|
|
4
|
+
import { controlPositionClasses } from './utils';
|
|
5
|
+
|
|
6
|
+
export interface MapLegendDiscreteProps {
|
|
7
|
+
position: ControlPosition;
|
|
8
|
+
legendItems: MapDiscreteLegendItem[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const MapLegendDiscrete = ({ position, legendItems }: MapLegendDiscreteProps) => {
|
|
12
|
+
return (
|
|
13
|
+
<div className={controlPositionClasses[position]}>
|
|
14
|
+
<div className="leaflet-control bento-charts--map--legend">
|
|
15
|
+
<ul>
|
|
16
|
+
{legendItems.map(({ label, color }, i) => (
|
|
17
|
+
<li key={i}>
|
|
18
|
+
<span
|
|
19
|
+
className="bento-charts--map--legend--patch"
|
|
20
|
+
style={{ backgroundColor: color ?? `rgba(255, 255, 255, 0)` }}
|
|
21
|
+
/>
|
|
22
|
+
{label}
|
|
23
|
+
</li>
|
|
24
|
+
))}
|
|
25
|
+
</ul>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default MapLegendDiscrete;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ControlPosition } from 'leaflet';
|
|
2
|
+
|
|
3
|
+
export const controlPositionClasses: { [x in ControlPosition]: string } = {
|
|
4
|
+
bottomleft: 'leaflet-bottom leaflet-left',
|
|
5
|
+
bottomright: 'leaflet-bottom leaflet-right',
|
|
6
|
+
topleft: 'leaflet-top leaflet-left',
|
|
7
|
+
topright: 'leaflet-bottom leaflet-right',
|
|
8
|
+
};
|
|
@@ -113,7 +113,6 @@ export const TICK_MARGIN = 5; // vertical spacing between tick line and tick lab
|
|
|
113
113
|
// pie chart
|
|
114
114
|
export const CHART_ASPECT_RATIO = 1.4;
|
|
115
115
|
export const LABEL_THRESHOLD = 0.05;
|
|
116
|
-
export const MAX_LABEL_CHARS = 14;
|
|
117
116
|
export const OTHER_THRESHOLD = 0.01;
|
|
118
117
|
|
|
119
118
|
// ################### UTIL CONSTANTS ###################
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
// Disable unused item linting in WebStorm:
|
|
2
|
+
// noinspection JSUnusedGlobalSymbols
|
|
3
|
+
|
|
4
|
+
// Categorical charts
|
|
1
5
|
export { default as BarChart } from './Components/Charts/BentoBarChart';
|
|
2
6
|
export { default as PieChart } from './Components/Charts/BentoPie';
|
|
3
7
|
|
|
8
|
+
// Maps are not included in index.ts - instead, they need to be included from `bento-charts/maps`.
|
|
9
|
+
// This way, we can have optional peer dependencies.
|
|
10
|
+
|
|
4
11
|
export { default as ChartConfigProvider } from './ChartConfigProvider';
|
|
5
12
|
export * from './types/chartTypes';
|
package/src/maps.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '*.png';
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
.bento-charts--map--legend {
|
|
2
|
+
background-color: white;
|
|
3
|
+
padding: 12px;
|
|
4
|
+
border-radius: 12px;
|
|
5
|
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
6
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
|
|
7
|
+
}
|
|
8
|
+
.leaflet-bottom .bento-charts--map--legend {
|
|
9
|
+
margin-bottom: 28px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.bento-charts--map--legend ul {
|
|
13
|
+
list-style: none;
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 0;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: 6px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.bento-charts--map--legend--patch {
|
|
22
|
+
display: inline-block;
|
|
23
|
+
width: 1rem;
|
|
24
|
+
height: 1rem;
|
|
25
|
+
border: 1px solid white;
|
|
26
|
+
vertical-align: top;
|
|
27
|
+
margin-right: 12px;
|
|
28
|
+
border-radius: 3px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.bento-charts--map--legend--scale {
|
|
32
|
+
height: 100px;
|
|
33
|
+
display: flex;
|
|
34
|
+
gap: 12px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.bento-charts--continuous-scale {
|
|
38
|
+
width: 1rem;
|
|
39
|
+
height: 100%;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.bento-charts--map--legend--values {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
}
|
|
46
|
+
.bento-charts--map--legend--values > span:first-of-type {
|
|
47
|
+
flex: 1;
|
|
48
|
+
}
|
package/src/types/chartTypes.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { PieProps, BarProps } from 'recharts';
|
|
1
|
+
import type { PieProps, BarProps } from 'recharts';
|
|
2
2
|
|
|
3
|
-
export type
|
|
3
|
+
export type CategoricalChartDataType = CategoricalChartDataItem[];
|
|
4
4
|
|
|
5
|
-
export interface
|
|
5
|
+
export interface CategoricalChartDataItem {
|
|
6
6
|
x: string;
|
|
7
7
|
y: number;
|
|
8
8
|
}
|
|
@@ -36,7 +36,7 @@ export type FilterCallback<T> = (value: T, index: number, array: T[]) => boolean
|
|
|
36
36
|
export type UnitaryMapCallback<T> = (value: T, index: number, array: T[]) => T;
|
|
37
37
|
// export type BinaryMapCallback<T, U> = (value: T, index: number, array: T[]) => U;
|
|
38
38
|
|
|
39
|
-
export type ChartFilterCallback = FilterCallback<
|
|
39
|
+
export type ChartFilterCallback = FilterCallback<CategoricalChartDataItem>;
|
|
40
40
|
|
|
41
41
|
export type SupportedLng = 'en' | 'fr';
|
|
42
42
|
|
|
@@ -51,23 +51,27 @@ export type TranslationObject = {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
// ################### COMPONENT PROPS #####################
|
|
54
|
-
interface
|
|
55
|
-
data: ChartDataType;
|
|
54
|
+
export interface BaseChartComponentProps {
|
|
56
55
|
height: number;
|
|
57
56
|
preFilter?: ChartFilterCallback;
|
|
58
|
-
dataMap?: UnitaryMapCallback<
|
|
57
|
+
dataMap?: UnitaryMapCallback<CategoricalChartDataItem>;
|
|
59
58
|
postFilter?: ChartFilterCallback;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface BaseCategoricalChartProps extends BaseChartComponentProps {
|
|
62
|
+
data: CategoricalChartDataType;
|
|
60
63
|
removeEmpty?: boolean;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
export interface PieChartProps extends
|
|
66
|
+
export interface PieChartProps extends BaseCategoricalChartProps {
|
|
64
67
|
colorTheme?: keyof ChartTheme['pie'];
|
|
65
68
|
sort?: boolean;
|
|
66
69
|
onClick?: PieProps['onClick'];
|
|
67
70
|
chartThreshold?: number;
|
|
71
|
+
maxLabelChars?: number;
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
export interface BarChartProps extends
|
|
74
|
+
export interface BarChartProps extends BaseCategoricalChartProps {
|
|
71
75
|
colorTheme?: keyof ChartTheme['bar'];
|
|
72
76
|
title?: string;
|
|
73
77
|
units: string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface GeoJSONGeomPolygon {
|
|
2
|
+
type: 'Polygon';
|
|
3
|
+
coordinates: number[][][];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface BentoGeoJSONProperties {
|
|
7
|
+
title: string;
|
|
8
|
+
[x: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GeoJSONPolygonFeature {
|
|
12
|
+
type: 'Feature';
|
|
13
|
+
geometry: GeoJSONGeomPolygon;
|
|
14
|
+
properties: BentoGeoJSONProperties;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface GeoJSONPolygonOnlyFeatureCollection {
|
|
18
|
+
type: 'FeatureCollection';
|
|
19
|
+
features: GeoJSONPolygonFeature[];
|
|
20
|
+
[x: string]: unknown;
|
|
21
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import type { Feature as GeoJSONFeatureType } from 'geojson';
|
|
3
|
+
|
|
4
|
+
import { BaseChartComponentProps, CategoricalChartDataType } from './chartTypes';
|
|
5
|
+
import type { GeoJSONPolygonOnlyFeatureCollection } from './geoJSONTypes';
|
|
6
|
+
|
|
7
|
+
export interface GeoPointDataItem {
|
|
8
|
+
coordinates: [number, number];
|
|
9
|
+
title: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type PointMapOnClick = (point: GeoPointDataItem) => void;
|
|
13
|
+
|
|
14
|
+
type GeoJSONShapeOnClick = (shape: GeoJSONFeatureType) => void;
|
|
15
|
+
|
|
16
|
+
export interface BaseMapProps extends BaseChartComponentProps {
|
|
17
|
+
center: [number, number];
|
|
18
|
+
zoom: number;
|
|
19
|
+
tileLayer?: ReactElement;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PointMapProps extends BaseMapProps {
|
|
23
|
+
data: GeoPointDataItem[];
|
|
24
|
+
onClick?: PointMapOnClick;
|
|
25
|
+
renderPopupBody?: (p: GeoPointDataItem) => ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MapDiscreteLegendItem {
|
|
29
|
+
color: string | undefined;
|
|
30
|
+
label: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ChoroplethMapColorModeContinuous {
|
|
34
|
+
mode: 'continuous';
|
|
35
|
+
minColor: string;
|
|
36
|
+
maxColor: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ChoroplethMapColorModeDiscrete {
|
|
40
|
+
mode: 'discrete';
|
|
41
|
+
colorFunction: (x: number | undefined) => string;
|
|
42
|
+
legendItems: MapDiscreteLegendItem[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ChoroplethMapProps extends BaseMapProps {
|
|
46
|
+
data: CategoricalChartDataType; // heatmaps are 'categorical' + geographical
|
|
47
|
+
features: GeoJSONPolygonOnlyFeatureCollection;
|
|
48
|
+
colorMode: ChoroplethMapColorModeContinuous | ChoroplethMapColorModeDiscrete;
|
|
49
|
+
categoryProp: string;
|
|
50
|
+
onClick?: GeoJSONShapeOnClick;
|
|
51
|
+
renderPopupBody?: (f: GeoJSONFeatureType, d: number | undefined) => ReactNode;
|
|
52
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
4
|
+
|
|
5
|
+
const config = {
|
|
6
|
+
mode: 'development',
|
|
7
|
+
entry: './test/js/index.tsx',
|
|
8
|
+
output: {
|
|
9
|
+
path: __dirname + '/test/dist',
|
|
10
|
+
publicPath: '',
|
|
11
|
+
filename: 'js/[name][chunkhash].js',
|
|
12
|
+
},
|
|
13
|
+
module: {
|
|
14
|
+
rules: [
|
|
15
|
+
{ test: /\.[tj](sx|s)?$/, use: { loader: 'ts-loader' }, exclude: /node_modules/ },
|
|
16
|
+
{
|
|
17
|
+
test: /\.html$/i,
|
|
18
|
+
loader: 'html-loader',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
test: /\.css$/i,
|
|
22
|
+
use: ['style-loader', 'css-loader'],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
test: /\.(png|jpe?g|gif)$/i,
|
|
26
|
+
loader: 'file-loader',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
watchOptions: {
|
|
31
|
+
poll: 1000,
|
|
32
|
+
},
|
|
33
|
+
plugins: [
|
|
34
|
+
new HtmlWebpackPlugin({
|
|
35
|
+
title: 'Development',
|
|
36
|
+
inject: false,
|
|
37
|
+
template: 'test/index.ejs',
|
|
38
|
+
}),
|
|
39
|
+
],
|
|
40
|
+
optimization: {
|
|
41
|
+
runtimeChunk: 'single',
|
|
42
|
+
},
|
|
43
|
+
devtool: 'source-map',
|
|
44
|
+
devServer: {
|
|
45
|
+
static: './test/dist',
|
|
46
|
+
},
|
|
47
|
+
resolve: {
|
|
48
|
+
alias: {
|
|
49
|
+
'@': path.resolve(__dirname, 'src'),
|
|
50
|
+
},
|
|
51
|
+
extensions: ['.tsx', '.ts', '.js'],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
module.exports = (_env, argv) => {
|
|
56
|
+
if (argv.mode === 'development') {
|
|
57
|
+
config.devtool = 'inline-source-map';
|
|
58
|
+
}
|
|
59
|
+
return config;
|
|
60
|
+
};
|