bento-charts 2.3.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.
Files changed (62) hide show
  1. package/README.md +94 -23
  2. package/dist/ChartConfigProvider.d.ts +2 -2
  3. package/dist/ChartConfigProvider.js.map +1 -1
  4. package/dist/Components/Charts/BentoBarChart.js.map +1 -1
  5. package/dist/Components/Charts/BentoPie.js +33 -31
  6. package/dist/Components/Charts/BentoPie.js.map +1 -1
  7. package/dist/Components/Maps/BentoChoroplethMap.d.ts +3 -0
  8. package/dist/Components/Maps/BentoChoroplethMap.js +90 -0
  9. package/dist/Components/Maps/BentoChoroplethMap.js.map +1 -0
  10. package/dist/Components/Maps/BentoMapContainer.d.ts +7 -0
  11. package/dist/Components/Maps/BentoMapContainer.js +33 -0
  12. package/dist/Components/Maps/BentoMapContainer.js.map +1 -0
  13. package/dist/Components/Maps/BentoOSMTileLayer.d.ts +2 -0
  14. package/dist/Components/Maps/BentoOSMTileLayer.js +6 -0
  15. package/dist/Components/Maps/BentoOSMTileLayer.js.map +1 -0
  16. package/dist/Components/Maps/BentoPointMap.d.ts +3 -0
  17. package/dist/Components/Maps/BentoPointMap.js +25 -0
  18. package/dist/Components/Maps/BentoPointMap.js.map +1 -0
  19. package/dist/Components/Maps/controls/MapLegendContinuous.d.ts +10 -0
  20. package/dist/Components/Maps/controls/MapLegendContinuous.js +19 -0
  21. package/dist/Components/Maps/controls/MapLegendContinuous.js.map +1 -0
  22. package/dist/Components/Maps/controls/MapLegendDiscrete.d.ts +8 -0
  23. package/dist/Components/Maps/controls/MapLegendDiscrete.js +22 -0
  24. package/dist/Components/Maps/controls/MapLegendDiscrete.js.map +1 -0
  25. package/dist/Components/Maps/controls/utils.d.ts +4 -0
  26. package/dist/Components/Maps/controls/utils.js +7 -0
  27. package/dist/Components/Maps/controls/utils.js.map +1 -0
  28. package/dist/constants/mapConstants.d.ts +2 -0
  29. package/dist/constants/mapConstants.js +3 -0
  30. package/dist/constants/mapConstants.js.map +1 -0
  31. package/dist/index.js +5 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/maps.d.ts +3 -0
  34. package/dist/maps.js +5 -0
  35. package/dist/maps.js.map +1 -0
  36. package/dist/types/chartTypes.d.ts +11 -9
  37. package/dist/types/geoJSONTypes.d.ts +18 -0
  38. package/dist/types/geoJSONTypes.js +2 -0
  39. package/dist/types/geoJSONTypes.js.map +1 -0
  40. package/dist/types/mapTypes.d.ts +43 -0
  41. package/dist/types/mapTypes.js +2 -0
  42. package/dist/types/mapTypes.js.map +1 -0
  43. package/package.json +30 -4
  44. package/src/ChartConfigProvider.tsx +9 -2
  45. package/src/Components/Charts/BentoBarChart.tsx +2 -2
  46. package/src/Components/Charts/BentoPie.tsx +55 -53
  47. package/src/Components/Maps/BentoChoroplethMap.tsx +137 -0
  48. package/src/Components/Maps/BentoMapContainer.tsx +35 -0
  49. package/src/Components/Maps/BentoOSMTileLayer.tsx +7 -0
  50. package/src/Components/Maps/BentoPointMap.tsx +30 -0
  51. package/src/Components/Maps/controls/MapLegendContinuous.tsx +32 -0
  52. package/src/Components/Maps/controls/MapLegendDiscrete.tsx +31 -0
  53. package/src/Components/Maps/controls/utils.ts +8 -0
  54. package/src/constants/mapConstants.ts +4 -0
  55. package/src/index.ts +7 -0
  56. package/src/maps.ts +4 -0
  57. package/src/react-app-env.d.ts +1 -0
  58. package/src/styles.css +48 -0
  59. package/src/types/chartTypes.ts +12 -9
  60. package/src/types/geoJSONTypes.ts +21 -0
  61. package/src/types/mapTypes.ts +52 -0
  62. package/webpack.config.js +60 -0
@@ -0,0 +1,22 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import { controlPositionClasses } from './utils';
14
+ var MapLegendDiscrete = function (_a) {
15
+ var position = _a.position, legendItems = _a.legendItems;
16
+ return (_jsx("div", __assign({ className: controlPositionClasses[position] }, { children: _jsx("div", __assign({ className: "leaflet-control bento-charts--map--legend" }, { children: _jsx("ul", { children: legendItems.map(function (_a, i) {
17
+ var label = _a.label, color = _a.color;
18
+ return (_jsxs("li", { children: [_jsx("span", { className: "bento-charts--map--legend--patch", style: { backgroundColor: color !== null && color !== void 0 ? color : "rgba(255, 255, 255, 0)" } }), label] }, i));
19
+ }) }) })) })));
20
+ };
21
+ export default MapLegendDiscrete;
22
+ //# sourceMappingURL=MapLegendDiscrete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapLegendDiscrete.js","sourceRoot":"","sources":["../../../../src/Components/Maps/controls/MapLegendDiscrete.tsx"],"names":[],"mappings":";;;;;;;;;;;;AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAOjD,IAAM,iBAAiB,GAAG,UAAC,EAAiD;QAA/C,QAAQ,cAAA,EAAE,WAAW,iBAAA;IAChD,OAAO,CACL,uBAAK,SAAS,EAAE,sBAAsB,CAAC,QAAQ,CAAC,gBAC9C,uBAAK,SAAS,EAAC,2CAA2C,gBACxD,uBACG,WAAW,CAAC,GAAG,CAAC,UAAC,EAAgB,EAAE,CAAC;wBAAjB,KAAK,WAAA,EAAE,KAAK,WAAA;oBAAU,OAAA,CACxC,yBACE,eACE,SAAS,EAAC,kCAAkC,EAC5C,KAAK,EAAE,EAAE,eAAe,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,wBAAwB,EAAE,GAC7D,EACD,KAAK,KALC,CAAC,CAML,CACN;gBARyC,CAQzC,CAAC,GACC,IACD,IACF,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ControlPosition } from 'leaflet';
2
+ export declare const controlPositionClasses: {
3
+ [x in ControlPosition]: string;
4
+ };
@@ -0,0 +1,7 @@
1
+ export var controlPositionClasses = {
2
+ bottomleft: 'leaflet-bottom leaflet-left',
3
+ bottomright: 'leaflet-bottom leaflet-right',
4
+ topleft: 'leaflet-top leaflet-left',
5
+ topright: 'leaflet-bottom leaflet-right',
6
+ };
7
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/Components/Maps/controls/utils.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,IAAM,sBAAsB,GAAuC;IACxE,UAAU,EAAE,6BAA6B;IACzC,WAAW,EAAE,8BAA8B;IAC3C,OAAO,EAAE,0BAA0B;IACnC,QAAQ,EAAE,8BAA8B;CACzC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const OSM_TILE_LAYER_ATTRIBUTION = "\n&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors\n";
2
+ export declare const OSM_TILE_LAYER_TEMPLATE = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
@@ -0,0 +1,3 @@
1
+ export var OSM_TILE_LAYER_ATTRIBUTION = "\n&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors\n";
2
+ export var OSM_TILE_LAYER_TEMPLATE = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
3
+ //# sourceMappingURL=mapConstants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapConstants.js","sourceRoot":"","sources":["../../src/constants/mapConstants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,IAAM,0BAA0B,GAAG,6FAEzC,CAAC;AACF,MAAM,CAAC,IAAM,uBAAuB,GAAG,oDAAoD,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,10 @@
1
+ // Disable unused item linting in WebStorm:
2
+ // noinspection JSUnusedGlobalSymbols
3
+ // Categorical charts
1
4
  export { default as BarChart } from './Components/Charts/BentoBarChart';
2
5
  export { default as PieChart } from './Components/Charts/BentoPie';
6
+ // Maps are not included in index.ts - instead, they need to be included from `bento-charts/maps`.
7
+ // This way, we can have optional peer dependencies.
3
8
  export { default as ChartConfigProvider } from './ChartConfigProvider';
4
9
  export * from './types/chartTypes';
5
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvE,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,qCAAqC;AAErC,qBAAqB;AACrB,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAEnE,kGAAkG;AAClG,oDAAoD;AAEpD,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvE,cAAc,oBAAoB,CAAC"}
package/dist/maps.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { default as PointMap } from './Components/Maps/BentoPointMap';
2
+ export { default as ChoroplethMap } from './Components/Maps/BentoChoroplethMap';
3
+ export * from './types/mapTypes';
package/dist/maps.js ADDED
@@ -0,0 +1,5 @@
1
+ // Maps
2
+ export { default as PointMap } from './Components/Maps/BentoPointMap';
3
+ export { default as ChoroplethMap } from './Components/Maps/BentoChoroplethMap';
4
+ export * from './types/mapTypes';
5
+ //# sourceMappingURL=maps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maps.js","sourceRoot":"","sources":["../src/maps.ts"],"names":[],"mappings":"AAAA,OAAO;AACP,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAChF,cAAc,kBAAkB,CAAC"}
@@ -1,6 +1,6 @@
1
- import { PieProps, BarProps } from 'recharts';
2
- export type ChartDataType = ChartDataItem[];
3
- export interface ChartDataItem {
1
+ import type { PieProps, BarProps } from 'recharts';
2
+ export type CategoricalChartDataType = CategoricalChartDataItem[];
3
+ export interface CategoricalChartDataItem {
4
4
  x: string;
5
5
  y: number;
6
6
  }
@@ -33,7 +33,7 @@ export type ChartTheme = {
33
33
  };
34
34
  export type FilterCallback<T> = (value: T, index: number, array: T[]) => boolean;
35
35
  export type UnitaryMapCallback<T> = (value: T, index: number, array: T[]) => T;
36
- export type ChartFilterCallback = FilterCallback<ChartDataItem>;
36
+ export type ChartFilterCallback = FilterCallback<CategoricalChartDataItem>;
37
37
  export type SupportedLng = 'en' | 'fr';
38
38
  type TranslationWords = 'Count' | 'Other';
39
39
  export type LngDictionary = {
@@ -42,22 +42,24 @@ export type LngDictionary = {
42
42
  export type TranslationObject = {
43
43
  [key in SupportedLng]: LngDictionary;
44
44
  };
45
- interface BaseChartProps {
46
- data: ChartDataType;
45
+ export interface BaseChartComponentProps {
47
46
  height: number;
48
47
  preFilter?: ChartFilterCallback;
49
- dataMap?: UnitaryMapCallback<ChartDataItem>;
48
+ dataMap?: UnitaryMapCallback<CategoricalChartDataItem>;
50
49
  postFilter?: ChartFilterCallback;
50
+ }
51
+ interface BaseCategoricalChartProps extends BaseChartComponentProps {
52
+ data: CategoricalChartDataType;
51
53
  removeEmpty?: boolean;
52
54
  }
53
- export interface PieChartProps extends BaseChartProps {
55
+ export interface PieChartProps extends BaseCategoricalChartProps {
54
56
  colorTheme?: keyof ChartTheme['pie'];
55
57
  sort?: boolean;
56
58
  onClick?: PieProps['onClick'];
57
59
  chartThreshold?: number;
58
60
  maxLabelChars?: number;
59
61
  }
60
- export interface BarChartProps extends BaseChartProps {
62
+ export interface BarChartProps extends BaseCategoricalChartProps {
61
63
  colorTheme?: keyof ChartTheme['bar'];
62
64
  title?: string;
63
65
  units: string;
@@ -0,0 +1,18 @@
1
+ export interface GeoJSONGeomPolygon {
2
+ type: 'Polygon';
3
+ coordinates: number[][][];
4
+ }
5
+ export interface BentoGeoJSONProperties {
6
+ title: string;
7
+ [x: string]: unknown;
8
+ }
9
+ export interface GeoJSONPolygonFeature {
10
+ type: 'Feature';
11
+ geometry: GeoJSONGeomPolygon;
12
+ properties: BentoGeoJSONProperties;
13
+ }
14
+ export interface GeoJSONPolygonOnlyFeatureCollection {
15
+ type: 'FeatureCollection';
16
+ features: GeoJSONPolygonFeature[];
17
+ [x: string]: unknown;
18
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=geoJSONTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geoJSONTypes.js","sourceRoot":"","sources":["../../src/types/geoJSONTypes.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ import type { Feature as GeoJSONFeatureType } from 'geojson';
3
+ import { BaseChartComponentProps, CategoricalChartDataType } from './chartTypes';
4
+ import type { GeoJSONPolygonOnlyFeatureCollection } from './geoJSONTypes';
5
+ export interface GeoPointDataItem {
6
+ coordinates: [number, number];
7
+ title: string;
8
+ }
9
+ type PointMapOnClick = (point: GeoPointDataItem) => void;
10
+ type GeoJSONShapeOnClick = (shape: GeoJSONFeatureType) => void;
11
+ export interface BaseMapProps extends BaseChartComponentProps {
12
+ center: [number, number];
13
+ zoom: number;
14
+ tileLayer?: ReactElement;
15
+ }
16
+ export interface PointMapProps extends BaseMapProps {
17
+ data: GeoPointDataItem[];
18
+ onClick?: PointMapOnClick;
19
+ renderPopupBody?: (p: GeoPointDataItem) => ReactNode;
20
+ }
21
+ export interface MapDiscreteLegendItem {
22
+ color: string | undefined;
23
+ label: string;
24
+ }
25
+ export interface ChoroplethMapColorModeContinuous {
26
+ mode: 'continuous';
27
+ minColor: string;
28
+ maxColor: string;
29
+ }
30
+ export interface ChoroplethMapColorModeDiscrete {
31
+ mode: 'discrete';
32
+ colorFunction: (x: number | undefined) => string;
33
+ legendItems: MapDiscreteLegendItem[];
34
+ }
35
+ export interface ChoroplethMapProps extends BaseMapProps {
36
+ data: CategoricalChartDataType;
37
+ features: GeoJSONPolygonOnlyFeatureCollection;
38
+ colorMode: ChoroplethMapColorModeContinuous | ChoroplethMapColorModeDiscrete;
39
+ categoryProp: string;
40
+ onClick?: GeoJSONShapeOnClick;
41
+ renderPopupBody?: (f: GeoJSONFeatureType, d: number | undefined) => ReactNode;
42
+ }
43
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mapTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapTypes.js","sourceRoot":"","sources":["../../src/types/mapTypes.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "bento-charts",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Charts library for Bento-platform",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "npx tsc",
9
9
  "prepublishOnly": "npm run build",
10
- "lint": "npx eslint src/**",
11
- "buildpack": "rm ./packs/*.tgz && tsc && npm pack --pack-destination ./packs"
10
+ "lint": "npx eslint src",
11
+ "buildpack": "rm ./packs/*.tgz && tsc && npm pack --pack-destination ./packs",
12
+ "test": "npx webpack-dev-server"
12
13
  },
13
14
  "release": {
14
15
  "branches": [
@@ -37,26 +38,48 @@
37
38
  },
38
39
  "homepage": "https://github.com/bento-platform/Bento-Charts#readme",
39
40
  "peerDependencies": {
41
+ "leaflet": "^1.9.4",
40
42
  "react": ">=16.0.0",
41
43
  "react-dom": ">=14.0.0",
44
+ "react-leaflet": "^4.2.1",
42
45
  "recharts": "^2.4.3"
43
46
  },
47
+ "peerDependenciesMeta": {
48
+ "leaflet": {
49
+ "optional": true
50
+ },
51
+ "react-leafet": {
52
+ "optional": true
53
+ }
54
+ },
44
55
  "devDependencies": {
45
56
  "@commitlint/cli": "^17.4.4",
46
57
  "@commitlint/config-conventional": "^17.4.4",
47
58
  "@semantic-release/git": "^10.0.1",
59
+ "@types/leaflet": "^1.9.3",
48
60
  "@types/react": "^18.0.28",
49
61
  "@types/react-dom": "^18.0.10",
62
+ "@types/react-leaflet": "^3.0.0",
50
63
  "@types/recharts": "^1.8.24",
51
64
  "@typescript-eslint/eslint-plugin": "^5.56.0",
52
65
  "@typescript-eslint/parser": "^5.56.0",
66
+ "@webpack-cli/serve": "^2.0.5",
67
+ "antd": "^5.9.2",
68
+ "css-loader": "^6.8.1",
53
69
  "eslint": "^8.36.0",
54
70
  "eslint-plugin-react": "^7.32.2",
71
+ "file-loader": "^6.2.0",
72
+ "geojson": "^0.5.0",
73
+ "html-webpack-plugin": "^5.5.3",
55
74
  "husky": "^8.0.3",
56
75
  "prettier": "2.7.1",
57
76
  "semantic-release": "^20.1.3",
77
+ "style-loader": "^3.3.3",
58
78
  "ts-loader": "^9.4.2",
59
- "typescript": "^4.9.5"
79
+ "typescript": "^4.9.5",
80
+ "webpack": "^5.88.2",
81
+ "webpack-cli": "^5.1.4",
82
+ "webpack-dev-server": "^4.15.1"
60
83
  },
61
84
  "prettier": {
62
85
  "trailingComma": "es5",
@@ -64,5 +87,8 @@
64
87
  "semi": true,
65
88
  "singleQuote": true,
66
89
  "printWidth": 120
90
+ },
91
+ "dependencies": {
92
+ "d3-interpolate": "^3.0.1"
67
93
  }
68
94
  }
@@ -1,7 +1,7 @@
1
1
  import React, { useContext } from 'react';
2
2
 
3
3
  import { DEFAULT_CHART_THEME, defaultTranslationObject } from './constants/chartConstants';
4
- import { ChartTheme, LngDictionary, SupportedLng, TranslationObject } from './types/chartTypes';
4
+ import type { ChartTheme, LngDictionary, SupportedLng, TranslationObject } from './types/chartTypes';
5
5
 
6
6
  type ChartContextType = {
7
7
  theme: ChartTheme;
@@ -35,7 +35,14 @@ export function useChartMaxLabelChars() {
35
35
  return useContext(ChartContext).maxLabelChars;
36
36
  }
37
37
 
38
- const ChartConfigProvider = ({ theme, Lng, translationMap, children, globalThreshold, maxLabelChars }: ChartConfigProviderProps) => {
38
+ const ChartConfigProvider = ({
39
+ theme,
40
+ Lng,
41
+ translationMap,
42
+ children,
43
+ globalThreshold,
44
+ maxLabelChars,
45
+ }: ChartConfigProviderProps) => {
39
46
  let lang: SupportedLng = 'en';
40
47
  try {
41
48
  lang = Lng as SupportedLng;
@@ -13,7 +13,7 @@ import {
13
13
  TICK_MARGIN,
14
14
  } from '../../constants/chartConstants';
15
15
 
16
- import type { BarChartProps, ChartDataItem, TooltipPayload } from '../../types/chartTypes';
16
+ import type { BarChartProps, CategoricalChartDataItem, TooltipPayload } from '../../types/chartTypes';
17
17
  import { useChartTheme, useChartTranslation } from '../../ChartConfigProvider';
18
18
  import NoData from '../NoData';
19
19
 
@@ -39,7 +39,7 @@ const BentoBarChart = ({
39
39
  const t = useChartTranslation();
40
40
  const { fill: chartFill, missing } = useChartTheme().bar[colorTheme];
41
41
 
42
- const fill = (entry: ChartDataItem) => (entry.x === 'missing' ? missing : chartFill);
42
+ const fill = (entry: CategoricalChartDataItem) => (entry.x === 'missing' ? missing : chartFill);
43
43
 
44
44
  data = [...data];
45
45
  if (preFilter) data = data.filter(preFilter);
@@ -19,8 +19,8 @@ import {
19
19
  useChartTheme,
20
20
  useChartTranslation,
21
21
  useChartThreshold,
22
- useChartMaxLabelChars
23
- } from "../../ChartConfigProvider";
22
+ useChartMaxLabelChars,
23
+ } from '../../ChartConfigProvider';
24
24
  import { polarToCartesian } from '../../util/chartUtils';
25
25
  import NoData from '../NoData';
26
26
 
@@ -144,64 +144,66 @@ const toNumber = (val: number | string | undefined, defaultValue?: number): numb
144
144
  return defaultValue || 0;
145
145
  };
146
146
 
147
- const RenderLabel = (maxLabelChars: number): PieProps['label'] => (params: PieLabelRenderProps ) => { // eslint-disable-line
148
- const { fill, payload, index, activeIndex } = params;
149
- const percent = params.percent || 0;
150
- const midAngle = params.midAngle || 0;
147
+ const RenderLabel =
148
+ (maxLabelChars: number): PieProps['label'] =>
149
+ (params: PieLabelRenderProps) => { // eslint-disable-line
150
+ const { fill, payload, index, activeIndex } = params;
151
+ const percent = params.percent || 0;
152
+ const midAngle = params.midAngle || 0;
151
153
 
152
- // skip rendering this static label if the sector is selected.
153
- // this will let the 'renderActiveState' draw without overlapping.
154
- // also, skip rendering if segment is too small a percentage (avoids label clutter)
155
- if (index === activeIndex || percent < LABEL_THRESHOLD) {
156
- return;
157
- }
154
+ // skip rendering this static label if the sector is selected.
155
+ // this will let the 'renderActiveState' draw without overlapping.
156
+ // also, skip rendering if segment is too small a percentage (avoids label clutter)
157
+ if (index === activeIndex || percent < LABEL_THRESHOLD) {
158
+ return;
159
+ }
158
160
 
159
- const outerRadius = toNumber(params.outerRadius);
160
- const cx = toNumber(params.cx);
161
- const cy = toNumber(params.cy);
161
+ const outerRadius = toNumber(params.outerRadius);
162
+ const cx = toNumber(params.cx);
163
+ const cy = toNumber(params.cy);
162
164
 
163
- const name = payload.name === 'null' ? '(Empty)' : payload.name;
165
+ const name = payload.name === 'null' ? '(Empty)' : payload.name;
164
166
 
165
- const sin = Math.sin(-RADIAN * midAngle);
166
- const cos = Math.cos(-RADIAN * midAngle);
167
- const sx = cx + (outerRadius + 10) * cos;
168
- const sy = cy + (outerRadius + 10) * sin;
169
- const mx = cx + (outerRadius + 20) * cos;
170
- const my = cy + (outerRadius + 20) * sin;
171
- const ex = mx + (cos >= 0 ? 1 : -1) * 22;
172
- const ey = my;
173
- const textAnchor = cos >= 0 ? 'start' : 'end';
167
+ const sin = Math.sin(-RADIAN * midAngle);
168
+ const cos = Math.cos(-RADIAN * midAngle);
169
+ const sx = cx + (outerRadius + 10) * cos;
170
+ const sy = cy + (outerRadius + 10) * sin;
171
+ const mx = cx + (outerRadius + 20) * cos;
172
+ const my = cy + (outerRadius + 20) * sin;
173
+ const ex = mx + (cos >= 0 ? 1 : -1) * 22;
174
+ const ey = my;
175
+ const textAnchor = cos >= 0 ? 'start' : 'end';
174
176
 
175
- const currentTextStyle: CSS.Properties = {
176
- ...TEXT_STYLE,
177
- fontWeight: payload.selected ? 'bold' : 'normal',
178
- fontStyle: payload.name === 'null' ? 'italic' : 'normal',
179
- };
177
+ const currentTextStyle: CSS.Properties = {
178
+ ...TEXT_STYLE,
179
+ fontWeight: payload.selected ? 'bold' : 'normal',
180
+ fontStyle: payload.name === 'null' ? 'italic' : 'normal',
181
+ };
180
182
 
181
- const offsetRadius = 20;
182
- const startPoint = polarToCartesian(cx, cy, outerRadius, midAngle);
183
- const endPoint = polarToCartesian(cx, cy, outerRadius + offsetRadius, midAngle);
184
- const lineProps = {
185
- ...params,
186
- fill: 'none',
187
- stroke: fill,
188
- points: [startPoint, endPoint],
189
- };
183
+ const offsetRadius = 20;
184
+ const startPoint = polarToCartesian(cx, cy, outerRadius, midAngle);
185
+ const endPoint = polarToCartesian(cx, cy, outerRadius + offsetRadius, midAngle);
186
+ const lineProps = {
187
+ ...params,
188
+ fill: 'none',
189
+ stroke: fill,
190
+ points: [startPoint, endPoint],
191
+ };
190
192
 
191
- return (
192
- <g>
193
- <Curve {...lineProps} type="linear" className="recharts-pie-label-line" />
194
- <path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
195
- <circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
196
- <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey + 3} textAnchor={textAnchor} style={currentTextStyle}>
197
- {labelShortName(name, maxLabelChars)}
198
- </text>
199
- <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} dy={14} textAnchor={textAnchor} style={COUNT_TEXT_STYLE}>
200
- {`(${payload.value})`}
201
- </text>
202
- </g>
203
- );
204
- };
193
+ return (
194
+ <g>
195
+ <Curve {...lineProps} type="linear" className="recharts-pie-label-line" />
196
+ <path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
197
+ <circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
198
+ <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey + 3} textAnchor={textAnchor} style={currentTextStyle}>
199
+ {labelShortName(name, maxLabelChars)}
200
+ </text>
201
+ <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} dy={14} textAnchor={textAnchor} style={COUNT_TEXT_STYLE}>
202
+ {`(${payload.value})`}
203
+ </text>
204
+ </g>
205
+ );
206
+ };
205
207
 
206
208
  const RenderActiveLabel: PieProps['activeShape'] = (params) => {
207
209
  const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill } = params;
@@ -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;