@hisptz/dhis2-analytics 1.0.51 → 1.0.53
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/LICENSE +29 -0
- package/build/cjs/components/Visualization/components/AnalyticsDataProvider/index.js +8 -5
- package/build/es/components/Visualization/components/AnalyticsDataProvider/index.js +8 -5
- package/package.json +4 -4
- package/.gitignore +0 -5
- package/d2.config.js +0 -8
- package/i18n/en.pot +0 -439
- package/src/components/ChartAnalytics/ChartAnalytics.test.tsx +0 -51
- package/src/components/ChartAnalytics/components/DownloadMenu/components/Menu.tsx +0 -48
- package/src/components/ChartAnalytics/components/DownloadMenu/constants/menu.ts +0 -38
- package/src/components/ChartAnalytics/components/DownloadMenu/index.tsx +0 -65
- package/src/components/ChartAnalytics/components/DownloadMenu/interfaces/menu.ts +0 -1
- package/src/components/ChartAnalytics/hooks/useChart.ts +0 -35
- package/src/components/ChartAnalytics/index.tsx +0 -28
- package/src/components/ChartAnalytics/models/bar.ts +0 -20
- package/src/components/ChartAnalytics/models/column.ts +0 -52
- package/src/components/ChartAnalytics/models/index.ts +0 -111
- package/src/components/ChartAnalytics/models/line.ts +0 -31
- package/src/components/ChartAnalytics/models/multi-series.ts +0 -115
- package/src/components/ChartAnalytics/models/pie.ts +0 -54
- package/src/components/ChartAnalytics/services/export.ts +0 -38
- package/src/components/ChartAnalytics/styles/custom-highchart.css +0 -48
- package/src/components/ChartAnalytics/types/props.tsx +0 -48
- package/src/components/ChartAnalytics/utils/chart.ts +0 -128
- package/src/components/CircularProgressDashboard/CircularProgressIndicator.test.tsx +0 -9
- package/src/components/CircularProgressDashboard/index.tsx +0 -36
- package/src/components/CircularProgressDashboard/types/props.tsx +0 -17
- package/src/components/CustomPivotTable/components/Table/index.tsx +0 -23
- package/src/components/CustomPivotTable/components/TableBody/TableBody.module.css +0 -12
- package/src/components/CustomPivotTable/components/TableBody/index.tsx +0 -96
- package/src/components/CustomPivotTable/components/TableHeaders/TableHeaders.module.css +0 -10
- package/src/components/CustomPivotTable/components/TableHeaders/index.tsx +0 -94
- package/src/components/CustomPivotTable/index.tsx +0 -63
- package/src/components/CustomPivotTable/interfaces/index.ts +0 -1
- package/src/components/CustomPivotTable/services/engine.ts +0 -102
- package/src/components/CustomPivotTable/state/engine.tsx +0 -22
- package/src/components/Map/components/EarthEngineLayerConfiguration/EarthEngineLayerConfigModal.stories.tsx +0 -28
- package/src/components/Map/components/EarthEngineLayerConfiguration/EarthEngineLayerConfiguration.stories.tsx +0 -34
- package/src/components/Map/components/EarthEngineLayerConfiguration/index.tsx +0 -412
- package/src/components/Map/components/MapArea/index.tsx +0 -83
- package/src/components/Map/components/MapArea/interfaces/index.ts +0 -39
- package/src/components/Map/components/MapControls/components/CustomControl/index.tsx +0 -24
- package/src/components/Map/components/MapControls/components/DownloadControl/index.tsx +0 -11
- package/src/components/Map/components/MapControls/components/FullscreenControl/index.tsx +0 -7
- package/src/components/Map/components/MapControls/index.tsx +0 -24
- package/src/components/Map/components/MapLayer/components/BoundaryLayer/hooks/useBoundaryData.ts +0 -7
- package/src/components/Map/components/MapLayer/components/BoundaryLayer/index.tsx +0 -55
- package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/components/EarthEngineLegend.tsx +0 -74
- package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/constants/index.ts +0 -430
- package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/hooks/index.ts +0 -34
- package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/index.tsx +0 -185
- package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/interfaces/index.ts +0 -56
- package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/services/api.js +0 -34241
- package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/services/engine.ts +0 -431
- package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/utils/index.ts +0 -105
- package/src/components/Map/components/MapLayer/components/LegendArea/LegendArea.module.css +0 -12
- package/src/components/Map/components/MapLayer/components/LegendArea/components/LegendCardHeader/index.tsx +0 -17
- package/src/components/Map/components/MapLayer/components/LegendArea/index.tsx +0 -167
- package/src/components/Map/components/MapLayer/components/PointLayer/components/PointLegend/index.tsx +0 -44
- package/src/components/Map/components/MapLayer/components/PointLayer/hooks/index.ts +0 -8
- package/src/components/Map/components/MapLayer/components/PointLayer/index.tsx +0 -36
- package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/components/BubbleLegend/components/Bubble.tsx +0 -48
- package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/components/BubbleLegend/components/Bubbles.tsx +0 -150
- package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/components/BubbleLegend/index.tsx +0 -39
- package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/index.tsx +0 -57
- package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Choropleth/components/ChoroplethLegend.tsx +0 -43
- package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Choropleth/index.tsx +0 -38
- package/src/components/Map/components/MapLayer/components/ThematicLayer/components/CustomTooltip/index.tsx +0 -26
- package/src/components/Map/components/MapLayer/components/ThematicLayer/hooks/config.ts +0 -10
- package/src/components/Map/components/MapLayer/components/ThematicLayer/index.tsx +0 -46
- package/src/components/Map/components/MapLayer/components/ThematicLayer/styles/legends.css +0 -62
- package/src/components/Map/components/MapLayer/index.tsx +0 -32
- package/src/components/Map/components/MapLayer/interfaces/index.ts +0 -139
- package/src/components/Map/components/MapProvider/components/MapLayerProvider/hooks/index.tsx +0 -368
- package/src/components/Map/components/MapProvider/components/MapLayerProvider/index.tsx +0 -105
- package/src/components/Map/components/MapProvider/hooks/index.ts +0 -14
- package/src/components/Map/components/MapProvider/index.tsx +0 -93
- package/src/components/Map/components/MapUpdater/index.tsx +0 -8
- package/src/components/Map/components/ThematicLayerConfiguration/ThematicLayerConfigModal.stories.tsx +0 -28
- package/src/components/Map/components/ThematicLayerConfiguration/ThematicLayerConfiguration.stories.tsx +0 -34
- package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/components/ColorScale/index.tsx +0 -24
- package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/constants/colors.ts +0 -433
- package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/index.tsx +0 -50
- package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/styles/ColorScale.module.css +0 -15
- package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/styles/ColorScaleSelect.module.css +0 -12
- package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/utils/colors.ts +0 -91
- package/src/components/Map/components/ThematicLayerConfiguration/components/CustomLegend/index.tsx +0 -45
- package/src/components/Map/components/ThematicLayerConfiguration/components/IndicatorSelectorModal/index.tsx +0 -47
- package/src/components/Map/components/ThematicLayerConfiguration/components/LegendSetSelector/index.tsx +0 -57
- package/src/components/Map/components/ThematicLayerConfiguration/index.tsx +0 -248
- package/src/components/Map/constants/colors.ts +0 -434
- package/src/components/Map/constants/legendSet.ts +0 -19
- package/src/components/Map/hooks/map.ts +0 -47
- package/src/components/Map/index.tsx +0 -65
- package/src/components/Map/interfaces/index.ts +0 -57
- package/src/components/Map/state/index.tsx +0 -31
- package/src/components/Map/utils/colors.ts +0 -95
- package/src/components/Map/utils/helpers.ts +0 -15
- package/src/components/Map/utils/map.ts +0 -150
- package/src/components/SingleValueContainer/SingleValueContainer.test.tsx +0 -24
- package/src/components/SingleValueContainer/components/SingleValueItem/SingleValueItem.tsx +0 -46
- package/src/components/SingleValueContainer/components/SingleValueItem/SingleValuePercentage.tsx +0 -12
- package/src/components/SingleValueContainer/index.tsx +0 -37
- package/src/components/SingleValueContainer/styles/SingleValueContainer.module.css +0 -39
- package/src/components/SingleValueContainer/types/props.tsx +0 -16
- package/src/components/Visualization/components/AnalyticsDataProvider/index.tsx +0 -76
- package/src/components/Visualization/components/DimensionsProvider/index.tsx +0 -51
- package/src/components/Visualization/components/LayoutProvider/index.tsx +0 -34
- package/src/components/Visualization/components/VisualizationDimensionSelector/index.tsx +0 -59
- package/src/components/Visualization/components/VisualizationProvider/index.tsx +0 -31
- package/src/components/Visualization/components/VisualizationSelector/index.tsx +0 -157
- package/src/components/Visualization/components/VisualizationTypeProvider/index.tsx +0 -40
- package/src/components/Visualization/components/VisualizationTypeSelector/index.tsx +0 -46
- package/src/components/Visualization/index.tsx +0 -103
- package/src/index.ts +0 -6
- package/src/locales/en/translations.json +0 -138
- package/src/locales/index.js +0 -16
- package/tsconfig.build.json +0 -46
- package/tsconfig.json +0 -51
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import {colors, IconLegend24, Popper, Portal} from "@dhis2/ui";
|
|
2
|
-
import {ControlPosition} from "leaflet";
|
|
3
|
-
import {compact, head} from "lodash";
|
|
4
|
-
import React, {useEffect, useRef, useState} from "react";
|
|
5
|
-
import {MapLegendConfig} from "../../../MapArea/interfaces";
|
|
6
|
-
import {CustomControl} from "../../../MapControls/components/CustomControl";
|
|
7
|
-
import {
|
|
8
|
-
CustomBubbleLayer,
|
|
9
|
-
CustomGoogleEngineLayer,
|
|
10
|
-
CustomPointLayer,
|
|
11
|
-
CustomThematicLayer,
|
|
12
|
-
SUPPORTED_EARTH_ENGINE_LAYERS
|
|
13
|
-
} from "../../interfaces";
|
|
14
|
-
import PointLegend from "../PointLayer/components/PointLegend";
|
|
15
|
-
import BubbleLegend from "../ThematicLayer/components/Bubble/components/BubbleLegend";
|
|
16
|
-
import ChoroplethLegend from "../ThematicLayer/components/Choropleth/components/ChoroplethLegend";
|
|
17
|
-
import EarthEngineLegend from "../GoogleEngineLayer/components/EarthEngineLegend";
|
|
18
|
-
import classes from "./LegendArea.module.css";
|
|
19
|
-
import {usePrintMedia} from "../../../../hooks/map";
|
|
20
|
-
|
|
21
|
-
const TOOLTIP_OFFSET = 4;
|
|
22
|
-
|
|
23
|
-
function getLegendComponent(layer: CustomThematicLayer | CustomPointLayer | CustomGoogleEngineLayer) {
|
|
24
|
-
if (layer.type === "point") {
|
|
25
|
-
return <PointLegend name={layer.label} />;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (SUPPORTED_EARTH_ENGINE_LAYERS.includes(layer.type)) {
|
|
29
|
-
return <EarthEngineLegend name={layer.name ?? ""} layer={layer as CustomGoogleEngineLayer} />;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const { type, enabled, control, dataItem, name, data, legends } = (layer as CustomThematicLayer) ?? {};
|
|
33
|
-
|
|
34
|
-
if (!enabled || !control) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
switch (type) {
|
|
38
|
-
case "bubble":
|
|
39
|
-
return (
|
|
40
|
-
<BubbleLegend
|
|
41
|
-
radius={(layer as CustomBubbleLayer)?.radius ?? { min: 0, max: 50 }}
|
|
42
|
-
legends={legends ?? []}
|
|
43
|
-
name={name ?? dataItem.displayName}
|
|
44
|
-
data={data}
|
|
45
|
-
dataItem={head(data)?.dataItem ?? dataItem}
|
|
46
|
-
/>
|
|
47
|
-
);
|
|
48
|
-
case "choropleth":
|
|
49
|
-
return <ChoroplethLegend legends={legends ?? []} name={name ?? dataItem.displayName} data={data} dataItem={head(data)?.dataItem ?? dataItem} />;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function CollapsedLegendIcon({ onCollapse, name }: { name: string; onCollapse: () => void }) {
|
|
54
|
-
const openDelay = 200;
|
|
55
|
-
const closeDelay = 200;
|
|
56
|
-
const [openTooltip, setOpenTooltip] = useState(false);
|
|
57
|
-
const openTimerRef = useRef<any>(null);
|
|
58
|
-
const closeTimerRef = useRef<any>(null);
|
|
59
|
-
const ref = useRef<HTMLDivElement>(null);
|
|
60
|
-
|
|
61
|
-
const hideModifier = { name: "hide" };
|
|
62
|
-
const offsetModifier = {
|
|
63
|
-
name: "offset",
|
|
64
|
-
options: {
|
|
65
|
-
offset: [0, TOOLTIP_OFFSET],
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const flipModifier = {
|
|
70
|
-
name: "flip",
|
|
71
|
-
options: { altBoundary: true },
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const onMouseOver = () => {
|
|
75
|
-
clearTimeout(closeTimerRef.current);
|
|
76
|
-
|
|
77
|
-
openTimerRef.current = setTimeout(() => {
|
|
78
|
-
setOpenTooltip(true);
|
|
79
|
-
}, openDelay);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const onMouseOut = () => {
|
|
83
|
-
clearTimeout(openTimerRef.current);
|
|
84
|
-
|
|
85
|
-
closeTimerRef.current = setTimeout(() => {
|
|
86
|
-
setOpenTooltip(false);
|
|
87
|
-
}, closeDelay);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
useEffect(
|
|
91
|
-
() => () => {
|
|
92
|
-
clearTimeout(openTimerRef.current);
|
|
93
|
-
clearTimeout(closeTimerRef.current);
|
|
94
|
-
},
|
|
95
|
-
[]
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<div ref={ref} onMouseOver={onMouseOver} onMouseOut={onMouseOut} onClick={onCollapse} style={{ width: 28, height: 28 }} className="legend-card collapsed">
|
|
100
|
-
<IconLegend24 />
|
|
101
|
-
{openTooltip && (
|
|
102
|
-
<Portal className={classes["map-tooltip"]}>
|
|
103
|
-
<Popper className={classes["map-tooltip"]} reference={ref} modifiers={[offsetModifier, flipModifier, hideModifier]}>
|
|
104
|
-
<div
|
|
105
|
-
style={{
|
|
106
|
-
backgroundColor: `${colors.grey900}`,
|
|
107
|
-
borderRadius: 3,
|
|
108
|
-
color: `${colors.white}`,
|
|
109
|
-
padding: "4px 6px",
|
|
110
|
-
}}
|
|
111
|
-
data-test={`content`}>
|
|
112
|
-
{name}
|
|
113
|
-
</div>
|
|
114
|
-
</Popper>
|
|
115
|
-
</Portal>
|
|
116
|
-
)}
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function Legend({ children, collapsible }: { children: React.ReactElement; collapsible: boolean }) {
|
|
122
|
-
const [collapsed, setCollapsed] = useState(collapsible);
|
|
123
|
-
const inPrintMode = usePrintMedia();
|
|
124
|
-
const onCollapse = () => {
|
|
125
|
-
if (collapsible) {
|
|
126
|
-
setCollapsed((prevState) => !prevState);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const name = head(React.Children.toArray(children) as React.ReactElement[])?.props.name;
|
|
131
|
-
|
|
132
|
-
const shouldCollapse = collapsed && !inPrintMode;
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<div className="w-100">
|
|
136
|
-
{shouldCollapse ? (
|
|
137
|
-
<CollapsedLegendIcon name={name} onCollapse={onCollapse} />
|
|
138
|
-
) : (
|
|
139
|
-
React.Children.map(children, (child) => React.cloneElement(child, { collapsible, onCollapse }))
|
|
140
|
-
)}
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export default function LegendArea({
|
|
146
|
-
layers,
|
|
147
|
-
legends: legendConfig,
|
|
148
|
-
}: {
|
|
149
|
-
layers: Array<CustomThematicLayer | CustomPointLayer | CustomGoogleEngineLayer>;
|
|
150
|
-
position: ControlPosition;
|
|
151
|
-
legends?: MapLegendConfig;
|
|
152
|
-
}) {
|
|
153
|
-
const legends: JSX.Element[] = compact(layers.filter((layer) => layer.enabled).map(getLegendComponent));
|
|
154
|
-
const { position, collapsible } = legendConfig ?? {};
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<CustomControl position={position}>
|
|
158
|
-
<div className="column gap-16 align-items-end">
|
|
159
|
-
{legends?.map((legend: any, index) => (
|
|
160
|
-
<Legend collapsible={collapsible ?? true} key={`${index}-map-legend`}>
|
|
161
|
-
{legend}
|
|
162
|
-
</Legend>
|
|
163
|
-
))}
|
|
164
|
-
</div>
|
|
165
|
-
</CustomControl>
|
|
166
|
-
);
|
|
167
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {useConfig} from "@dhis2/app-runtime";
|
|
2
|
-
import {Divider} from "@dhis2/ui";
|
|
3
|
-
import React, {forwardRef} from "react";
|
|
4
|
-
import {getIconUrl} from "../../../../../../utils/helpers";
|
|
5
|
-
import LegendCardHeader from "../../../LegendArea/components/LegendCardHeader";
|
|
6
|
-
import {usePointLayer} from "../../hooks";
|
|
7
|
-
|
|
8
|
-
function PointLegends({ orgUnitGroups, icon, label }: { orgUnitGroups: { name: string; symbol: string }[]; icon?: string; label?: string }) {
|
|
9
|
-
const { baseUrl } = useConfig();
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<div style={{ minWidth: 100, alignItems: "flex-start" }} className="w-100 p-8 legend-list column">
|
|
13
|
-
{icon && (
|
|
14
|
-
<div key={`${icon}-legend`} className="row gap-16 align-items-center">
|
|
15
|
-
<img height={20} width={20} alt={`${name}-icon`} src={getIconUrl(icon, { baseUrl })} />
|
|
16
|
-
<p>{label}</p>
|
|
17
|
-
</div>
|
|
18
|
-
)}
|
|
19
|
-
{orgUnitGroups.map(({ name, symbol }) => {
|
|
20
|
-
return (
|
|
21
|
-
<div key={`${name}-legend`} className="row gap-16 align-items-center">
|
|
22
|
-
<img height={20} width={20} alt={`${name}-icon`} src={getIconUrl(symbol ?? "", { baseUrl })} />
|
|
23
|
-
<p>{name}</p>
|
|
24
|
-
</div>
|
|
25
|
-
);
|
|
26
|
-
})}
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function PointLegend({ collapsible, onCollapse }: any, ref: React.LegacyRef<HTMLDivElement>) {
|
|
32
|
-
const pointLayer = usePointLayer();
|
|
33
|
-
const { label, style } = pointLayer ?? {};
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div ref={ref} className="legend-card">
|
|
37
|
-
<LegendCardHeader collapsible={collapsible} onCollapse={onCollapse} title={label ?? "Points"} />
|
|
38
|
-
<Divider margin={"0"} />
|
|
39
|
-
<PointLegends label={label} orgUnitGroups={style?.orgUnitGroups ?? []} icon={style?.icon} />
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export default forwardRef(PointLegend);
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import {find} from "lodash";
|
|
2
|
-
import {useMapLayers} from "../../../../MapProvider/hooks";
|
|
3
|
-
import {CustomPointLayer} from "../../../interfaces";
|
|
4
|
-
|
|
5
|
-
export function usePointLayer() {
|
|
6
|
-
const { layers } = useMapLayers();
|
|
7
|
-
return find(layers, ["type", "point"]) as CustomPointLayer;
|
|
8
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import {useConfig} from "@dhis2/app-runtime";
|
|
2
|
-
import i18n from "@dhis2/d2-i18n";
|
|
3
|
-
import L from "leaflet";
|
|
4
|
-
import React from "react";
|
|
5
|
-
import {GeoJSON, LayerGroup, LayersControl, Popup, Tooltip} from "react-leaflet";
|
|
6
|
-
import {PointOrgUnit} from "../../../../interfaces";
|
|
7
|
-
import {getIcon, getIconUrl} from "../../../../utils/helpers";
|
|
8
|
-
import {usePointLayer} from "./hooks";
|
|
9
|
-
|
|
10
|
-
export function PointLayer() {
|
|
11
|
-
const pointLayer = usePointLayer();
|
|
12
|
-
const { enabled, label, points: orgUnits, style } = pointLayer ?? {};
|
|
13
|
-
const { baseUrl } = useConfig();
|
|
14
|
-
return (
|
|
15
|
-
<LayersControl.Overlay checked={enabled} name={label ?? i18n.t("Points")}>
|
|
16
|
-
<LayerGroup>
|
|
17
|
-
{orgUnits?.map((area: PointOrgUnit) => {
|
|
18
|
-
return (
|
|
19
|
-
<GeoJSON
|
|
20
|
-
pointToLayer={(_, coordinates) => {
|
|
21
|
-
return L.marker(coordinates, { icon: getIcon(getIconUrl(area.icon.icon ?? style?.icon, { baseUrl })) });
|
|
22
|
-
}}
|
|
23
|
-
data={area.geoJSON}
|
|
24
|
-
interactive
|
|
25
|
-
key={`${area.id}-polygon`}>
|
|
26
|
-
<Tooltip>{area.name}</Tooltip>
|
|
27
|
-
<Popup minWidth={80}>
|
|
28
|
-
<h3>{area.name}</h3>
|
|
29
|
-
</Popup>
|
|
30
|
-
</GeoJSON>
|
|
31
|
-
);
|
|
32
|
-
})}
|
|
33
|
-
</LayerGroup>
|
|
34
|
-
</LayersControl.Overlay>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import {guideLength, textPadding} from "./Bubbles";
|
|
3
|
-
|
|
4
|
-
export interface BubbleProps {
|
|
5
|
-
radius: number;
|
|
6
|
-
maxRadius: number;
|
|
7
|
-
text?: string;
|
|
8
|
-
textAlign?: "left" | "right";
|
|
9
|
-
color?: string;
|
|
10
|
-
stroke?: string;
|
|
11
|
-
pattern?: string;
|
|
12
|
-
gap?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const Bubble = ({ radius, maxRadius, text, textAlign, color, stroke, pattern }: BubbleProps) => {
|
|
16
|
-
const leftAlign = textAlign === "left";
|
|
17
|
-
const x = maxRadius;
|
|
18
|
-
const y = maxRadius * 2 - radius;
|
|
19
|
-
const x2 = leftAlign ? x - maxRadius - guideLength : x + maxRadius + guideLength;
|
|
20
|
-
const y2 = maxRadius * 2 - radius * 2;
|
|
21
|
-
const textX = x2 + (leftAlign ? -textPadding : textPadding);
|
|
22
|
-
const textAnchor = leftAlign ? "end" : "start";
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<g>
|
|
26
|
-
<circle
|
|
27
|
-
cx={x}
|
|
28
|
-
cy={y}
|
|
29
|
-
r={radius}
|
|
30
|
-
stroke={stroke || "#000"}
|
|
31
|
-
style={{
|
|
32
|
-
fill: pattern ? `url(#${pattern})` : color || "none",
|
|
33
|
-
strokeWidth: 0.5,
|
|
34
|
-
}}
|
|
35
|
-
/>
|
|
36
|
-
{text && (
|
|
37
|
-
<g>
|
|
38
|
-
<line x1={x} x2={x2} y1={y2} y2={y2} stroke="black" style={{ strokeDasharray: "2, 2", strokeWidth: 0.5 }} />
|
|
39
|
-
<text x={textX} y={y2} textAnchor={textAnchor} alignmentBaseline="middle" style={{ fontSize: 12 }}>
|
|
40
|
-
{text}
|
|
41
|
-
</text>
|
|
42
|
-
</g>
|
|
43
|
-
)}
|
|
44
|
-
</g>
|
|
45
|
-
);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export default Bubble;
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import i18n from "@dhis2/d2-i18n";
|
|
2
|
-
import {scaleSqrt} from "d3-scale";
|
|
3
|
-
import {reduce} from "lodash";
|
|
4
|
-
import React, {memo} from "react";
|
|
5
|
-
import {getContrastColor} from "../../../../../../../../../utils/colors";
|
|
6
|
-
import {getLongestTextLength} from "../../../../../../../../../utils/helpers";
|
|
7
|
-
import Bubble, {BubbleProps} from "./Bubble";
|
|
8
|
-
|
|
9
|
-
const style = {
|
|
10
|
-
paddingTop: 10,
|
|
11
|
-
display: "flex",
|
|
12
|
-
alignItems: "center",
|
|
13
|
-
justifyContent: "center",
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const legendWidth = 200;
|
|
17
|
-
const digitWidth = 6.8;
|
|
18
|
-
export const guideLength = 16;
|
|
19
|
-
export const textPadding = 4;
|
|
20
|
-
|
|
21
|
-
const Bubbles = ({ radiusLow, radiusHigh, color, classes }: { radiusLow: number; radiusHigh: number; color?: string; classes: Array<any> }) => {
|
|
22
|
-
const height = radiusHigh * 2 + 4;
|
|
23
|
-
const scale = scaleSqrt().range([radiusLow, radiusHigh]);
|
|
24
|
-
const radiusMid = scale(0.5);
|
|
25
|
-
|
|
26
|
-
if (isNaN(radiusLow) || isNaN(radiusHigh)) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let bubbles: Array<BubbleProps> = [];
|
|
31
|
-
|
|
32
|
-
// If color legend
|
|
33
|
-
if (Array.isArray(classes) && classes.length) {
|
|
34
|
-
const startValue = classes[0].startValue;
|
|
35
|
-
const endValue = classes[classes.length - 1].endValue;
|
|
36
|
-
const itemScale = scale.domain([startValue, endValue]);
|
|
37
|
-
|
|
38
|
-
bubbles = [...classes].reverse().map((c) => ({
|
|
39
|
-
radius: itemScale(c.endValue),
|
|
40
|
-
maxRadius: radiusHigh,
|
|
41
|
-
color: c.color,
|
|
42
|
-
text: String(c.endValue),
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
|
-
// Add the smallest bubble for the lowest value
|
|
46
|
-
bubbles.push({
|
|
47
|
-
radius: itemScale(startValue),
|
|
48
|
-
maxRadius: radiusHigh,
|
|
49
|
-
text: String(startValue),
|
|
50
|
-
});
|
|
51
|
-
} else {
|
|
52
|
-
// If single color
|
|
53
|
-
const stroke = color && getContrastColor(color);
|
|
54
|
-
|
|
55
|
-
bubbles = [
|
|
56
|
-
{
|
|
57
|
-
radius: radiusHigh,
|
|
58
|
-
maxRadius: radiusHigh,
|
|
59
|
-
color,
|
|
60
|
-
stroke,
|
|
61
|
-
text: i18n.t("Max"),
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
radius: radiusMid,
|
|
65
|
-
maxRadius: radiusHigh,
|
|
66
|
-
color,
|
|
67
|
-
stroke,
|
|
68
|
-
text: i18n.t("Mid"),
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
radius: radiusLow,
|
|
72
|
-
maxRadius: radiusHigh,
|
|
73
|
-
color,
|
|
74
|
-
stroke,
|
|
75
|
-
text: i18n.t("Min"),
|
|
76
|
-
},
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Calculate the pixel length of the longest number
|
|
81
|
-
let textLength = Math.ceil(Math.max(getLongestTextLength(classes, "startValue"), getLongestTextLength(classes, "endValue")) * digitWidth);
|
|
82
|
-
|
|
83
|
-
// Calculate the total length if numbers are alternate on each side
|
|
84
|
-
const alternateLength = (radiusHigh + guideLength + textPadding + textLength) * 2;
|
|
85
|
-
|
|
86
|
-
let smallestGap = reduce(bubbles, (prev, curr: any, i) => {
|
|
87
|
-
const gap = prev.radius - curr.radius;
|
|
88
|
-
const smallestGap = prev.gap === undefined || gap < prev.gap ? gap : prev.gap;
|
|
89
|
-
|
|
90
|
-
return i === bubbles.length - 1
|
|
91
|
-
? Math.round(smallestGap * 2)
|
|
92
|
-
: {
|
|
93
|
-
radius: curr.radius,
|
|
94
|
-
gap: smallestGap,
|
|
95
|
-
};
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const alternateFit = alternateLength < legendWidth;
|
|
99
|
-
|
|
100
|
-
const alternate = alternateFit && smallestGap > 5 && smallestGap < 12;
|
|
101
|
-
|
|
102
|
-
if (!alternateFit) {
|
|
103
|
-
smallestGap = smallestGap / 2;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Too cramped to show number for each bubble
|
|
107
|
-
if (smallestGap < 4) {
|
|
108
|
-
const [maxBubble] = bubbles;
|
|
109
|
-
const minBubble = bubbles[bubbles.length - 1];
|
|
110
|
-
const gap = maxBubble.radius - minBubble.radius;
|
|
111
|
-
const showNumbers = [0]; // Always show the largest number
|
|
112
|
-
|
|
113
|
-
if (gap > 4) {
|
|
114
|
-
showNumbers.push(bubbles.length - 1);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (gap > 15) {
|
|
118
|
-
const midRadius = minBubble.radius + gap / 2;
|
|
119
|
-
|
|
120
|
-
// Find the closest bubble above the mid-radius
|
|
121
|
-
const midBubble = bubbles.reduce((prev, curr) => (curr.radius >= midRadius && curr.radius - midRadius < prev.radius - midRadius ? curr : prev));
|
|
122
|
-
|
|
123
|
-
showNumbers.push(bubbles.indexOf(midBubble));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
bubbles.forEach((b, i) => {
|
|
127
|
-
if (!showNumbers.includes(i)) {
|
|
128
|
-
delete b.text;
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
textLength = Math.ceil(getLongestTextLength(bubbles, "text") * digitWidth);
|
|
134
|
-
|
|
135
|
-
const offset = textLength + guideLength + textPadding;
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<div style={style}>
|
|
139
|
-
<svg width={legendWidth} height={height + 50}>
|
|
140
|
-
<g transform={`translate(${alternate ? offset : "16"} 24)`}>
|
|
141
|
-
{bubbles.map((bubble, i) => (
|
|
142
|
-
<Bubble key={i} {...bubble} textAlign={alternate && i % 2 == 0 ? "left" : "right"} />
|
|
143
|
-
))}
|
|
144
|
-
</g>
|
|
145
|
-
</svg>
|
|
146
|
-
</div>
|
|
147
|
-
);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
export default memo(Bubbles);
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import {Divider} from "@dhis2/ui";
|
|
2
|
-
import type {Legend} from "@hisptz/dhis2-utils";
|
|
3
|
-
import React, {forwardRef} from "react";
|
|
4
|
-
import {ThematicLayerData, ThematicLayerDataItem} from "../../../../../../interfaces";
|
|
5
|
-
import LegendCardHeader from "../../../../../LegendArea/components/LegendCardHeader";
|
|
6
|
-
import Bubbles from "./components/Bubbles";
|
|
7
|
-
|
|
8
|
-
function BubbleLegend(
|
|
9
|
-
{
|
|
10
|
-
radius,
|
|
11
|
-
dataItem,
|
|
12
|
-
data,
|
|
13
|
-
name,
|
|
14
|
-
collapsible,
|
|
15
|
-
onCollapse,
|
|
16
|
-
legends,
|
|
17
|
-
}: {
|
|
18
|
-
radius: { min: number; max: number };
|
|
19
|
-
dataItem: ThematicLayerDataItem;
|
|
20
|
-
data: ThematicLayerData[];
|
|
21
|
-
name?: string;
|
|
22
|
-
collapsible?: boolean;
|
|
23
|
-
onCollapse?: () => void;
|
|
24
|
-
legends: Legend[];
|
|
25
|
-
},
|
|
26
|
-
ref: React.LegacyRef<HTMLDivElement> | undefined
|
|
27
|
-
) {
|
|
28
|
-
return (
|
|
29
|
-
<div className="legend-card" ref={ref}>
|
|
30
|
-
<LegendCardHeader title={dataItem.displayName} onCollapse={onCollapse} collapsible={collapsible} />
|
|
31
|
-
<Divider margin={"0"} />
|
|
32
|
-
<div className="legend-list pt-8">
|
|
33
|
-
<Bubbles classes={legends.reverse()} radiusHigh={radius.max} radiusLow={radius.min} color={"#FF0000"} />
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export default forwardRef(BubbleLegend);
|
package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/index.tsx
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import {colors} from "@dhis2/ui";
|
|
2
|
-
import type {Legend} from "@hisptz/dhis2-utils";
|
|
3
|
-
import {geoJSON} from "leaflet";
|
|
4
|
-
import React, {useMemo} from "react";
|
|
5
|
-
import {CircleMarker} from "react-leaflet";
|
|
6
|
-
import {getColorFromLegendSet, highlightFeature, resetHighlight} from "../../../../../../utils/map";
|
|
7
|
-
import {ThematicLayerData} from "../../../../interfaces";
|
|
8
|
-
import CustomTooltip from "../CustomTooltip";
|
|
9
|
-
|
|
10
|
-
const defaultStyle = {
|
|
11
|
-
weight: 1,
|
|
12
|
-
};
|
|
13
|
-
const highlightStyle = {
|
|
14
|
-
weight: 2,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export default function Bubble({
|
|
18
|
-
data,
|
|
19
|
-
highestData,
|
|
20
|
-
legends,
|
|
21
|
-
radius,
|
|
22
|
-
}: {
|
|
23
|
-
data: ThematicLayerData;
|
|
24
|
-
highestData: number;
|
|
25
|
-
legends: Legend[];
|
|
26
|
-
radius?: { min: number; max: number };
|
|
27
|
-
}) {
|
|
28
|
-
const { orgUnit, data: value } = data ?? {};
|
|
29
|
-
|
|
30
|
-
const geoJSONObject = orgUnit.geoJSON;
|
|
31
|
-
const center = geoJSON(geoJSONObject).getBounds().getCenter();
|
|
32
|
-
|
|
33
|
-
const circleRadius = useMemo(() => {
|
|
34
|
-
return ((value ?? 0) * (radius?.max ?? 50)) / highestData;
|
|
35
|
-
}, [radius, data, highestData]);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<>
|
|
39
|
-
<CircleMarker
|
|
40
|
-
interactive
|
|
41
|
-
eventHandlers={{
|
|
42
|
-
mouseover: (e) => highlightFeature(e, highlightStyle),
|
|
43
|
-
mouseout: (e) => resetHighlight(e, defaultStyle),
|
|
44
|
-
}}
|
|
45
|
-
pathOptions={{
|
|
46
|
-
fillColor: getColorFromLegendSet(legends, data.data),
|
|
47
|
-
fillOpacity: 1,
|
|
48
|
-
color: colors.grey900,
|
|
49
|
-
weight: 1,
|
|
50
|
-
}}
|
|
51
|
-
radius={circleRadius}
|
|
52
|
-
center={center}>
|
|
53
|
-
<CustomTooltip data={data} />
|
|
54
|
-
</CircleMarker>
|
|
55
|
-
</>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import "../../../styles/legends.css";
|
|
2
|
-
import {Divider} from "@dhis2/ui";
|
|
3
|
-
import type {Legend} from "@hisptz/dhis2-utils";
|
|
4
|
-
import React, {forwardRef} from "react";
|
|
5
|
-
import {getLegendCount} from "../../../../../../../utils/map";
|
|
6
|
-
import {ThematicLayerData, ThematicLayerDataItem} from "../../../../../interfaces";
|
|
7
|
-
import LegendCardHeader from "../../../../LegendArea/components/LegendCardHeader";
|
|
8
|
-
|
|
9
|
-
export function LegendItem({ legend, value }: { legend: { startValue: number; endValue: number; color: string }; value: number }) {
|
|
10
|
-
return (
|
|
11
|
-
<div className="legend-item">
|
|
12
|
-
<div className="legend-item-color" style={{ backgroundColor: legend.color }} />
|
|
13
|
-
<div className="legend-item-label">{`${legend.startValue} - ${legend.endValue}`}</div>
|
|
14
|
-
<div className="legend-item-value">{`(${value})`}</div>
|
|
15
|
-
</div>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function ChoroplethLegend(
|
|
20
|
-
{
|
|
21
|
-
dataItem,
|
|
22
|
-
data,
|
|
23
|
-
name,
|
|
24
|
-
collapsible,
|
|
25
|
-
onCollapse,
|
|
26
|
-
legends,
|
|
27
|
-
}: { data: ThematicLayerData[]; dataItem: ThematicLayerDataItem; name?: string; collapsible?: boolean; onCollapse?: () => void; legends: Legend[] },
|
|
28
|
-
ref: React.LegacyRef<HTMLDivElement> | undefined
|
|
29
|
-
) {
|
|
30
|
-
return (
|
|
31
|
-
<div className="legend-card" ref={ref}>
|
|
32
|
-
<LegendCardHeader title={dataItem.displayName} collapsible={collapsible} onCollapse={onCollapse} />
|
|
33
|
-
<Divider margin={"0"} />
|
|
34
|
-
<div className="legend-list pt-8">
|
|
35
|
-
{legends?.map((legend: any) => (
|
|
36
|
-
<LegendItem key={`${legend?.color}-legend-list`} legend={legend} value={getLegendCount(legend, data)} />
|
|
37
|
-
))}
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default forwardRef(ChoroplethLegend);
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import {colors} from "@dhis2/ui";
|
|
2
|
-
import type {Legend} from "@hisptz/dhis2-utils";
|
|
3
|
-
import React from "react";
|
|
4
|
-
import {GeoJSON} from "react-leaflet";
|
|
5
|
-
import {MapOrgUnit} from "../../../../../../interfaces";
|
|
6
|
-
import {getColorFromLegendSet, highlightFeature, resetHighlight} from "../../../../../../utils/map";
|
|
7
|
-
import {ThematicLayerDataItem} from "../../../../interfaces";
|
|
8
|
-
import CustomTooltip from "../CustomTooltip";
|
|
9
|
-
|
|
10
|
-
const defaultStyle = {
|
|
11
|
-
weight: 1,
|
|
12
|
-
};
|
|
13
|
-
const highlightStyle = {
|
|
14
|
-
weight: 2,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export default function Choropleth({ data, legends }: { data: { orgUnit: MapOrgUnit; data?: number; dataItem: ThematicLayerDataItem }; legends: Legend[] }) {
|
|
18
|
-
const { orgUnit } = data;
|
|
19
|
-
return (
|
|
20
|
-
<>
|
|
21
|
-
<GeoJSON
|
|
22
|
-
data={orgUnit.geoJSON}
|
|
23
|
-
eventHandlers={{
|
|
24
|
-
mouseover: (e) => highlightFeature(e, highlightStyle),
|
|
25
|
-
mouseout: (e) => resetHighlight(e, defaultStyle),
|
|
26
|
-
}}
|
|
27
|
-
pathOptions={{
|
|
28
|
-
fillColor: getColorFromLegendSet(legends, data.data),
|
|
29
|
-
fillOpacity: 1,
|
|
30
|
-
color: colors.grey900,
|
|
31
|
-
weight: 1,
|
|
32
|
-
}}
|
|
33
|
-
key={`${data.dataItem.id}-layer`}>
|
|
34
|
-
<CustomTooltip data={data} />
|
|
35
|
-
</GeoJSON>
|
|
36
|
-
</>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import i18n from "@dhis2/d2-i18n";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import {Pane, Popup, Tooltip} from "react-leaflet";
|
|
4
|
-
import {useMapPeriods} from "../../../../../MapProvider/hooks";
|
|
5
|
-
import {ThematicLayerData} from "../../../../interfaces";
|
|
6
|
-
|
|
7
|
-
export default function CustomTooltip({ data: dataObject }: { data: ThematicLayerData }) {
|
|
8
|
-
const { dataItem, orgUnit, data } = dataObject ?? {};
|
|
9
|
-
const { periods } = useMapPeriods() ?? {};
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<Pane name={`${dataItem.displayName}-${orgUnit.id}-popup-pane`} pane="popupPane">
|
|
13
|
-
<Tooltip>
|
|
14
|
-
{orgUnit?.name} ({data})
|
|
15
|
-
</Tooltip>
|
|
16
|
-
<Popup minWidth={80}>
|
|
17
|
-
<h3 style={{ margin: 0 }}>{orgUnit?.name}</h3>
|
|
18
|
-
<div>{dataItem?.displayName}</div>
|
|
19
|
-
<div>{periods?.map((period) => period.name).join(",")}</div>
|
|
20
|
-
<div>
|
|
21
|
-
{i18n.t("Value")}: {data}
|
|
22
|
-
</div>
|
|
23
|
-
</Popup>
|
|
24
|
-
</Pane>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import {find} from "lodash";
|
|
2
|
-
import {useContext} from "react";
|
|
3
|
-
import {MapLayersContext} from "../../../../../state";
|
|
4
|
-
import {CustomThematicLayer} from "../../../interfaces";
|
|
5
|
-
|
|
6
|
-
export default function useThematicLayer(layerId: string): CustomThematicLayer | undefined {
|
|
7
|
-
const { layers } = useContext(MapLayersContext);
|
|
8
|
-
|
|
9
|
-
return find(layers as CustomThematicLayer[], ["id", layerId]);
|
|
10
|
-
}
|