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.
- package/README.md +94 -23
- package/dist/ChartConfigProvider.d.ts +2 -2
- package/dist/ChartConfigProvider.js.map +1 -1
- package/dist/Components/Charts/BentoBarChart.js.map +1 -1
- package/dist/Components/Charts/BentoPie.js +33 -31
- 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/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 +11 -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 +9 -2
- package/src/Components/Charts/BentoBarChart.tsx +2 -2
- package/src/Components/Charts/BentoPie.tsx +55 -53
- 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/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 +12 -9
- package/src/types/geoJSONTypes.ts +21 -0
- package/src/types/mapTypes.ts +52 -0
- 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 @@
|
|
|
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 @@
|
|
|
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
package/dist/maps.js
ADDED
package/dist/maps.js.map
ADDED
|
@@ -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
|
|
3
|
-
export interface
|
|
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<
|
|
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
|
|
46
|
-
data: ChartDataType;
|
|
45
|
+
export interface BaseChartComponentProps {
|
|
47
46
|
height: number;
|
|
48
47
|
preFilter?: ChartFilterCallback;
|
|
49
|
-
dataMap?: UnitaryMapCallback<
|
|
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
|
|
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
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|
+
"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 = ({
|
|
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,
|
|
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:
|
|
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
|
|
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 =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
const outerRadius = toNumber(params.outerRadius);
|
|
162
|
+
const cx = toNumber(params.cx);
|
|
163
|
+
const cy = toNumber(params.cy);
|
|
162
164
|
|
|
163
|
-
|
|
165
|
+
const name = payload.name === 'null' ? '(Empty)' : payload.name;
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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;
|