@hisptz/dhis2-analytics 1.0.48 → 1.0.51

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 (127) hide show
  1. package/.gitignore +5 -0
  2. package/build/cjs/components/ChartAnalytics/ChartAnalytics.test.js +1 -1
  3. package/build/cjs/components/ChartAnalytics/models/bar.js +24 -0
  4. package/build/cjs/components/ChartAnalytics/models/index.js +1 -1
  5. package/build/cjs/components/ChartAnalytics/utils/chart.js +5 -0
  6. package/build/es/components/ChartAnalytics/ChartAnalytics.test.js +1 -1
  7. package/build/es/components/ChartAnalytics/models/bar.js +16 -0
  8. package/build/es/components/ChartAnalytics/models/index.js +1 -1
  9. package/build/es/components/ChartAnalytics/utils/chart.js +5 -0
  10. package/build/types/components/ChartAnalytics/models/bar.d.ts +8 -0
  11. package/build/types/components/ChartAnalytics/types/props.d.ts +1 -1
  12. package/d2.config.js +8 -0
  13. package/i18n/en.pot +439 -0
  14. package/package.json +5 -5
  15. package/src/components/ChartAnalytics/ChartAnalytics.test.tsx +51 -0
  16. package/src/components/ChartAnalytics/components/DownloadMenu/components/Menu.tsx +48 -0
  17. package/src/components/ChartAnalytics/components/DownloadMenu/constants/menu.ts +38 -0
  18. package/src/components/ChartAnalytics/components/DownloadMenu/index.tsx +65 -0
  19. package/src/components/ChartAnalytics/components/DownloadMenu/interfaces/menu.ts +1 -0
  20. package/src/components/ChartAnalytics/hooks/useChart.ts +35 -0
  21. package/src/components/ChartAnalytics/index.tsx +28 -0
  22. package/src/components/ChartAnalytics/models/bar.ts +20 -0
  23. package/src/components/ChartAnalytics/models/column.ts +52 -0
  24. package/src/components/ChartAnalytics/models/index.ts +111 -0
  25. package/src/components/ChartAnalytics/models/line.ts +31 -0
  26. package/src/components/ChartAnalytics/models/multi-series.ts +115 -0
  27. package/src/components/ChartAnalytics/models/pie.ts +54 -0
  28. package/src/components/ChartAnalytics/services/export.ts +38 -0
  29. package/src/components/ChartAnalytics/styles/custom-highchart.css +48 -0
  30. package/src/components/ChartAnalytics/types/props.tsx +48 -0
  31. package/src/components/ChartAnalytics/utils/chart.ts +128 -0
  32. package/src/components/CircularProgressDashboard/CircularProgressIndicator.test.tsx +9 -0
  33. package/src/components/CircularProgressDashboard/index.tsx +36 -0
  34. package/src/components/CircularProgressDashboard/types/props.tsx +17 -0
  35. package/src/components/CustomPivotTable/components/Table/index.tsx +23 -0
  36. package/src/components/CustomPivotTable/components/TableBody/TableBody.module.css +12 -0
  37. package/src/components/CustomPivotTable/components/TableBody/index.tsx +96 -0
  38. package/src/components/CustomPivotTable/components/TableHeaders/TableHeaders.module.css +10 -0
  39. package/src/components/CustomPivotTable/components/TableHeaders/index.tsx +94 -0
  40. package/src/components/CustomPivotTable/index.tsx +63 -0
  41. package/src/components/CustomPivotTable/interfaces/index.ts +1 -0
  42. package/src/components/CustomPivotTable/services/engine.ts +102 -0
  43. package/src/components/CustomPivotTable/state/engine.tsx +22 -0
  44. package/src/components/Map/components/EarthEngineLayerConfiguration/EarthEngineLayerConfigModal.stories.tsx +28 -0
  45. package/src/components/Map/components/EarthEngineLayerConfiguration/EarthEngineLayerConfiguration.stories.tsx +34 -0
  46. package/src/components/Map/components/EarthEngineLayerConfiguration/index.tsx +412 -0
  47. package/src/components/Map/components/MapArea/index.tsx +83 -0
  48. package/src/components/Map/components/MapArea/interfaces/index.ts +39 -0
  49. package/src/components/Map/components/MapControls/components/CustomControl/index.tsx +24 -0
  50. package/src/components/Map/components/MapControls/components/DownloadControl/index.tsx +11 -0
  51. package/src/components/Map/components/MapControls/components/FullscreenControl/index.tsx +7 -0
  52. package/src/components/Map/components/MapControls/index.tsx +24 -0
  53. package/src/components/Map/components/MapLayer/components/BoundaryLayer/hooks/useBoundaryData.ts +7 -0
  54. package/src/components/Map/components/MapLayer/components/BoundaryLayer/index.tsx +55 -0
  55. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/components/EarthEngineLegend.tsx +74 -0
  56. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/constants/index.ts +430 -0
  57. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/hooks/index.ts +34 -0
  58. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/index.tsx +185 -0
  59. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/interfaces/index.ts +56 -0
  60. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/services/api.js +34241 -0
  61. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/services/engine.ts +431 -0
  62. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/utils/index.ts +105 -0
  63. package/src/components/Map/components/MapLayer/components/LegendArea/LegendArea.module.css +12 -0
  64. package/src/components/Map/components/MapLayer/components/LegendArea/components/LegendCardHeader/index.tsx +17 -0
  65. package/src/components/Map/components/MapLayer/components/LegendArea/index.tsx +167 -0
  66. package/src/components/Map/components/MapLayer/components/PointLayer/components/PointLegend/index.tsx +44 -0
  67. package/src/components/Map/components/MapLayer/components/PointLayer/hooks/index.ts +8 -0
  68. package/src/components/Map/components/MapLayer/components/PointLayer/index.tsx +36 -0
  69. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/components/BubbleLegend/components/Bubble.tsx +48 -0
  70. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/components/BubbleLegend/components/Bubbles.tsx +150 -0
  71. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/components/BubbleLegend/index.tsx +39 -0
  72. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/index.tsx +57 -0
  73. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Choropleth/components/ChoroplethLegend.tsx +43 -0
  74. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Choropleth/index.tsx +38 -0
  75. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/CustomTooltip/index.tsx +26 -0
  76. package/src/components/Map/components/MapLayer/components/ThematicLayer/hooks/config.ts +10 -0
  77. package/src/components/Map/components/MapLayer/components/ThematicLayer/index.tsx +46 -0
  78. package/src/components/Map/components/MapLayer/components/ThematicLayer/styles/legends.css +62 -0
  79. package/src/components/Map/components/MapLayer/index.tsx +32 -0
  80. package/src/components/Map/components/MapLayer/interfaces/index.ts +139 -0
  81. package/src/components/Map/components/MapProvider/components/MapLayerProvider/hooks/index.tsx +368 -0
  82. package/src/components/Map/components/MapProvider/components/MapLayerProvider/index.tsx +105 -0
  83. package/src/components/Map/components/MapProvider/hooks/index.ts +14 -0
  84. package/src/components/Map/components/MapProvider/index.tsx +93 -0
  85. package/src/components/Map/components/MapUpdater/index.tsx +8 -0
  86. package/src/components/Map/components/ThematicLayerConfiguration/ThematicLayerConfigModal.stories.tsx +28 -0
  87. package/src/components/Map/components/ThematicLayerConfiguration/ThematicLayerConfiguration.stories.tsx +34 -0
  88. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/components/ColorScale/index.tsx +24 -0
  89. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/constants/colors.ts +433 -0
  90. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/index.tsx +50 -0
  91. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/styles/ColorScale.module.css +15 -0
  92. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/styles/ColorScaleSelect.module.css +12 -0
  93. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/utils/colors.ts +91 -0
  94. package/src/components/Map/components/ThematicLayerConfiguration/components/CustomLegend/index.tsx +45 -0
  95. package/src/components/Map/components/ThematicLayerConfiguration/components/IndicatorSelectorModal/index.tsx +47 -0
  96. package/src/components/Map/components/ThematicLayerConfiguration/components/LegendSetSelector/index.tsx +57 -0
  97. package/src/components/Map/components/ThematicLayerConfiguration/index.tsx +248 -0
  98. package/src/components/Map/constants/colors.ts +434 -0
  99. package/src/components/Map/constants/legendSet.ts +19 -0
  100. package/src/components/Map/hooks/map.ts +47 -0
  101. package/src/components/Map/index.tsx +65 -0
  102. package/src/components/Map/interfaces/index.ts +57 -0
  103. package/src/components/Map/state/index.tsx +31 -0
  104. package/src/components/Map/utils/colors.ts +95 -0
  105. package/src/components/Map/utils/helpers.ts +15 -0
  106. package/src/components/Map/utils/map.ts +150 -0
  107. package/src/components/SingleValueContainer/SingleValueContainer.test.tsx +24 -0
  108. package/src/components/SingleValueContainer/components/SingleValueItem/SingleValueItem.tsx +46 -0
  109. package/src/components/SingleValueContainer/components/SingleValueItem/SingleValuePercentage.tsx +12 -0
  110. package/src/components/SingleValueContainer/index.tsx +37 -0
  111. package/src/components/SingleValueContainer/styles/SingleValueContainer.module.css +39 -0
  112. package/src/components/SingleValueContainer/types/props.tsx +16 -0
  113. package/src/components/Visualization/components/AnalyticsDataProvider/index.tsx +76 -0
  114. package/src/components/Visualization/components/DimensionsProvider/index.tsx +51 -0
  115. package/src/components/Visualization/components/LayoutProvider/index.tsx +34 -0
  116. package/src/components/Visualization/components/VisualizationDimensionSelector/index.tsx +59 -0
  117. package/src/components/Visualization/components/VisualizationProvider/index.tsx +31 -0
  118. package/src/components/Visualization/components/VisualizationSelector/index.tsx +157 -0
  119. package/src/components/Visualization/components/VisualizationTypeProvider/index.tsx +40 -0
  120. package/src/components/Visualization/components/VisualizationTypeSelector/index.tsx +46 -0
  121. package/src/components/Visualization/index.tsx +103 -0
  122. package/src/index.ts +6 -0
  123. package/src/locales/en/translations.json +138 -0
  124. package/src/locales/index.js +16 -0
  125. package/tsconfig.build.json +46 -0
  126. package/tsconfig.json +51 -0
  127. package/LICENSE +0 -29
@@ -0,0 +1,48 @@
1
+ import {FlyoutMenu, Menu, MenuDivider, MenuItem, Popover} from "@dhis2/ui";
2
+ import {isEmpty, map} from "lodash";
3
+ import React from "react";
4
+ import {chartMenuSections} from "../constants/menu";
5
+ import {ChartExportMenuItem} from "../interfaces/menu";
6
+
7
+ export function ChartMenu({
8
+ menuRef,
9
+ onClick,
10
+ onClose,
11
+ exclude = [],
12
+ }: {
13
+ menuRef: HTMLDivElement;
14
+ onClick: (action: string) => void;
15
+ onClose: () => void;
16
+ exclude?: ChartExportMenuItem[];
17
+ }) {
18
+ const onMenuClick = (action: string) => () => {
19
+ onClick(action);
20
+ onClose();
21
+ };
22
+
23
+ const menuSections = map(chartMenuSections, (section) => {
24
+ return {
25
+ ...section,
26
+ menuItems: section.menuItems.filter((item: any) => !exclude.includes(item.name)),
27
+ };
28
+ }).filter((section) => !isEmpty(section.menuItems));
29
+
30
+ return (
31
+ <Popover onClickOutside={onClose} onClose={onClose} reference={menuRef}>
32
+ <FlyoutMenu>
33
+ <Menu>
34
+ {menuSections?.map((section, index) => {
35
+ return (
36
+ <>
37
+ {section.menuItems?.map(({ name, label }) => {
38
+ return <MenuItem key={`${name}-menu-item`} dataTest={`download-${name}`} label={label} onClick={onMenuClick(name)} />;
39
+ })}
40
+ {index !== menuSections.length - 1 && <MenuDivider />}
41
+ </>
42
+ );
43
+ })}
44
+ </Menu>
45
+ </FlyoutMenu>
46
+ </Popover>
47
+ );
48
+ }
@@ -0,0 +1,38 @@
1
+ import i18n from "@dhis2/d2-i18n";
2
+
3
+ export const chartMenuSections = [
4
+ {
5
+ name: "download",
6
+ menuItems: [
7
+ {
8
+ name: "png",
9
+ label: i18n.t("Download PNG"),
10
+ },
11
+ {
12
+ name: "jpeg",
13
+ label: i18n.t("Download JPEG"),
14
+ },
15
+ {
16
+ name: "svg",
17
+ label: i18n.t("Download SVG"),
18
+ },
19
+ {
20
+ name: "pdf",
21
+ label: i18n.t("Download PDF"),
22
+ },
23
+ ],
24
+ },
25
+ {
26
+ name: "view",
27
+ menuItems: [
28
+ {
29
+ name: "table",
30
+ label: i18n.t("View as table"),
31
+ },
32
+ {
33
+ name: "full-screen",
34
+ label: i18n.t("View full screen"),
35
+ },
36
+ ],
37
+ },
38
+ ];
@@ -0,0 +1,65 @@
1
+ import {IconMore24} from "@dhis2/ui";
2
+ import HighchartsReact from "highcharts-react-official";
3
+ import React, {useRef, useState} from "react";
4
+ import {onCSVDownload, onFullScreenView, onImageDownload, onPDFDownload, onViewAsTable} from "../../services/export";
5
+ import {ChartMenu} from "./components/Menu";
6
+ import {ChartExportMenuItem} from "./interfaces/menu";
7
+
8
+ export function ChartDownloadMenu({
9
+ chartRef,
10
+ exclude,
11
+ icon,
12
+ }: {
13
+ chartRef: HighchartsReact.RefObject | null;
14
+ exclude?: ChartExportMenuItem[];
15
+ icon?: React.ReactNode;
16
+ }) {
17
+ const menuButtonRef = useRef<HTMLDivElement | null>(null);
18
+ const [menuRef, setMenuRef] = useState<HTMLDivElement | null>(null);
19
+ const toggleMenu = () => {
20
+ if (menuRef === null) {
21
+ setMenuRef(menuButtonRef.current);
22
+ } else {
23
+ setMenuRef(null);
24
+ }
25
+ };
26
+
27
+ const chart = chartRef;
28
+
29
+ const onMenuClick = (action: string) => {
30
+ if (chart) {
31
+ switch (action) {
32
+ case "png":
33
+ onImageDownload(chart, "png");
34
+ break;
35
+ case "jpeg":
36
+ onImageDownload(chart, "jpeg");
37
+ break;
38
+ case "svg":
39
+ onImageDownload(chart, "svg+xml");
40
+ break;
41
+ case "csv":
42
+ onCSVDownload(chart);
43
+ break;
44
+ case "pdf":
45
+ onPDFDownload(chart);
46
+ break;
47
+ case "table":
48
+ onViewAsTable(chart, true);
49
+ break;
50
+ case "full-screen":
51
+ onFullScreenView(chart);
52
+ break;
53
+ }
54
+ }
55
+ };
56
+
57
+ return (
58
+ <>
59
+ <div onClick={toggleMenu} ref={menuButtonRef}>
60
+ {icon ?? <IconMore24 />}
61
+ </div>
62
+ {menuRef && <ChartMenu exclude={exclude} onClick={onMenuClick} onClose={toggleMenu} menuRef={menuRef} />}
63
+ </>
64
+ );
65
+ }
@@ -0,0 +1 @@
1
+ export type ChartExportMenuItem = "png" | "jpeg" | "svg" | "csv" | "pdf" | "table" | "full-screen";
@@ -0,0 +1,35 @@
1
+ import type {Analytics} from "@hisptz/dhis2-utils";
2
+ import HighCharts from "highcharts";
3
+ import {useCallback, useEffect, useState} from "react";
4
+ import {DHIS2Chart} from "../models";
5
+ import {ChartConfig, ChartType} from "../types/props";
6
+ import {getChartInstance, updateLayout} from "../utils/chart";
7
+
8
+ export function useChart({ id, analytics, config }: { id: string; analytics: Analytics; config: ChartConfig }): {
9
+ chart?: HighCharts.Options;
10
+ changeChartType: (type: ChartType) => void;
11
+ } {
12
+ const [chart, setChart] = useState<HighCharts.Options | undefined>(getChartInstance(id, analytics, config).getOptions());
13
+
14
+ const changeChartType = useCallback(
15
+ (type: ChartType) => {
16
+ const updatedLayout = updateLayout(config, { type });
17
+ const updatedConfig = { ...config, layout: updatedLayout, type };
18
+ const chartInstance: DHIS2Chart = getChartInstance(id, analytics, updatedConfig);
19
+ setChart(chartInstance.getOptions());
20
+ },
21
+ [config, id, analytics]
22
+ );
23
+
24
+ useEffect(() => {
25
+ if (analytics && config) {
26
+ const chartInstance: DHIS2Chart = getChartInstance(id, analytics, config);
27
+ setChart(chartInstance.getOptions());
28
+ }
29
+ }, [analytics, config, id]);
30
+
31
+ return {
32
+ chart,
33
+ changeChartType,
34
+ };
35
+ }
@@ -0,0 +1,28 @@
1
+ import {uid} from "@hisptz/dhis2-utils";
2
+ import HighCharts from "highcharts";
3
+ import HighchartsReact from "highcharts-react-official";
4
+ import React, {forwardRef, useRef} from "react";
5
+ import {useChart} from "./hooks/useChart";
6
+ import "./styles/custom-highchart.css";
7
+ import {ChartAnalyticsProps} from "./types/props";
8
+
9
+ export * from "./services/export";
10
+ export * from "./types/props"
11
+ export * from "./components/DownloadMenu"
12
+
13
+ function ChartAnalyticsComponent({
14
+ analytics,
15
+ config,
16
+ containerProps
17
+ }: ChartAnalyticsProps, ref: React.ForwardedRef<HighchartsReact.RefObject>) {
18
+ const id = useRef(`${uid()}-chart-item`);
19
+ const {chart} = useChart({id: id.current, analytics, config});
20
+
21
+ if (!chart) {
22
+ return null;
23
+ }
24
+ return <HighchartsReact immutable ref={ref} containerProps={{id: id.current, ...(containerProps ?? {})}}
25
+ highcharts={HighCharts} options={{...chart}}/>;
26
+ }
27
+
28
+ export const ChartAnalytics: React.FC<ChartAnalyticsProps> = forwardRef(ChartAnalyticsComponent);
@@ -0,0 +1,20 @@
1
+ import {PlotOptions} from "highcharts";
2
+ import {DHIS2ColumnChart} from "./column";
3
+
4
+ export class DHIS2BarChart extends DHIS2ColumnChart {
5
+ getHighchartsType(): string {
6
+ return "bar";
7
+ }
8
+
9
+ }
10
+
11
+ export class DHIS2StackedBarChart extends DHIS2BarChart {
12
+ getPlotOptions(): PlotOptions {
13
+ return {
14
+ column: {
15
+ stacking: "normal",
16
+ ...super.getPlotOptions().column,
17
+ },
18
+ };
19
+ }
20
+ }
@@ -0,0 +1,52 @@
1
+ import {PlotOptions, SeriesOptionsType, XAxisOptions} from "highcharts";
2
+ import {getAllCategories, getPointSeries} from "../utils/chart";
3
+ import {DHIS2Chart} from "./index";
4
+
5
+ export class DHIS2ColumnChart extends DHIS2Chart {
6
+ getCategories(): any[] | undefined {
7
+ return undefined;
8
+ }
9
+
10
+ getHighchartsType(): string {
11
+ return "column";
12
+ }
13
+
14
+ getPlotOptions(): PlotOptions {
15
+ return {
16
+ column: {
17
+ dataLabels: {
18
+ enabled: true,
19
+ },
20
+ },
21
+ };
22
+ }
23
+
24
+ getSeries(): SeriesOptionsType[] {
25
+ return getPointSeries(this.analytics, this.config, "column");
26
+ }
27
+
28
+ getXAxis(): XAxisOptions | undefined {
29
+ return {
30
+ type: "category",
31
+ categories: getAllCategories(this.analytics, this.config),
32
+ crosshair: true,
33
+ labels: {
34
+ enabled: true,
35
+ },
36
+ title: { text: "" },
37
+ };
38
+ }
39
+ }
40
+
41
+ export class DHIS2StackedColumnChart extends DHIS2ColumnChart {
42
+ getPlotOptions(): PlotOptions {
43
+ return {
44
+ column: {
45
+ stacking: "normal",
46
+ ...super.getPlotOptions().column,
47
+ },
48
+ };
49
+ }
50
+ }
51
+
52
+
@@ -0,0 +1,111 @@
1
+ import type {Analytics} from "@hisptz/dhis2-utils";
2
+ import HighCharts from "highcharts";
3
+ import {ChartConfig} from "../types/props";
4
+
5
+ export abstract class DHIS2Chart {
6
+ id: string;
7
+ analytics: Analytics;
8
+ config: ChartConfig;
9
+
10
+ constructor(id: string, analytics: Analytics, config: ChartConfig) {
11
+ this.id = id;
12
+ this.analytics = analytics;
13
+ this.config = config;
14
+ }
15
+
16
+ abstract getHighchartsType(): string;
17
+
18
+ getChartConfig(): HighCharts.ChartOptions & any {
19
+ return {
20
+ renderTo: this.id,
21
+ zoomType: "xy",
22
+ type: this.getHighchartsType(),
23
+ height: this.config?.height,
24
+ styledMode: false,
25
+ };
26
+ }
27
+
28
+ getOptions(): HighCharts.Options {
29
+
30
+ const options = {
31
+ yAxis: this.getYAxis(),
32
+ chart: this.getChartConfig(),
33
+ colors: this.config?.colors ?? [
34
+ '#a8bf24',
35
+ '#518cc3',
36
+ '#d74554',
37
+ '#ff9e21',
38
+ '#968f8f',
39
+ '#ba3ba1',
40
+ '#ffda54',
41
+ '#45beae',
42
+ '#b98037',
43
+ '#676767',
44
+ '#6b2dd4',
45
+ '#47792c',
46
+ '#fcbdbd',
47
+ '#830000',
48
+ '#a5ffc0',
49
+ '#000078',
50
+ '#817c00',
51
+ '#bdf023',
52
+ '#fffac4',
53
+ ],
54
+ series: this.getSeries(),
55
+ plotOptions: this.getPlotOptions(),
56
+ title: {text: ""},
57
+ xAxis: this.getXAxis(),
58
+ exporting: this.getExporting(),
59
+ legend: {enabled: true},
60
+ credits: {enabled: false},
61
+ };
62
+
63
+ let overrides = {};
64
+
65
+ if (this.config?.highChartOverrides) {
66
+ overrides = {
67
+ ...((typeof this.config?.highChartOverrides === "object" ? this.config?.highChartOverrides ?? {} : this.config?.highChartOverrides(options)))
68
+ }
69
+ }
70
+
71
+ return {
72
+ ...options,
73
+ ...overrides
74
+ }
75
+ }
76
+
77
+ abstract getSeries(): HighCharts.SeriesOptionsType[];
78
+
79
+ abstract getPlotOptions(): HighCharts.PlotOptions;
80
+
81
+ abstract getXAxis(): HighCharts.XAxisOptions | undefined;
82
+
83
+ getYAxis(): HighCharts.YAxisOptions[] {
84
+ return [
85
+ {
86
+ title: {
87
+ text: "",
88
+ style: {color: "#000000", fontWeight: "normal", fontSize: "14px"},
89
+ },
90
+ labels: {enabled: true, style: {color: "#000000", fontWeight: "normal", fontSize: "14px"}},
91
+ plotLines: [
92
+ {color: "#000000", dashStyle: "Solid", width: 2, zIndex: 1000, label: {text: ""}},
93
+ {color: "#bbbbbb", dashStyle: "Solid", zIndex: 1000, width: 2, label: {text: ""}},
94
+ ],
95
+ },
96
+ ];
97
+ }
98
+
99
+ getExporting(): HighCharts.ExportingOptions {
100
+ const name = this.config?.name ?? "chart";
101
+ return {
102
+ filename: `${name}`,
103
+ sourceWidth: 1200,
104
+ buttons: {
105
+ contextButton: {
106
+ enabled: false,
107
+ },
108
+ },
109
+ };
110
+ }
111
+ }
@@ -0,0 +1,31 @@
1
+ import {PlotOptions, SeriesOptionsType, XAxisOptions} from "highcharts";
2
+ import {getAllCategories, getPointSeries} from "../utils/chart";
3
+ import {DHIS2Chart} from "./index";
4
+
5
+ export class DHIS2LineChart extends DHIS2Chart {
6
+ getHighchartsType(): string {
7
+ return "line";
8
+ }
9
+
10
+ getPlotOptions(): PlotOptions {
11
+ return {
12
+ line: {},
13
+ };
14
+ }
15
+
16
+ getSeries(): SeriesOptionsType[] {
17
+ return getPointSeries(this.analytics, this.config, "line");
18
+ }
19
+
20
+ getXAxis(): XAxisOptions | undefined {
21
+ return {
22
+ type: "category",
23
+ categories: getAllCategories(this.analytics, this.config),
24
+ crosshair: true,
25
+ labels: {
26
+ enabled: true,
27
+ },
28
+ title: { text: "" },
29
+ };
30
+ }
31
+ }
@@ -0,0 +1,115 @@
1
+ import {PlotOptions, SeriesOptionsType, XAxisOptions, YAxisOptions} from "highcharts";
2
+ import {compact, head} from "lodash";
3
+ import {MultiSeriesConfig} from "../types/props";
4
+ import {getAllCategories, getDimensionHeaderIndex} from "../utils/chart";
5
+ import {DHIS2Chart} from "./index";
6
+
7
+ export class DHIS2MultiSeriesChart extends DHIS2Chart {
8
+ getHighchartsType(): string {
9
+ return "";
10
+ }
11
+
12
+ getPlotOptions(): PlotOptions {
13
+ return {
14
+ column: {},
15
+ line: {},
16
+ };
17
+ }
18
+
19
+ getSeries(): SeriesOptionsType[] {
20
+ const analytics = this.analytics;
21
+ const config = this.config;
22
+
23
+ const categoryDimension = head(config.layout.category);
24
+ const seriesConfig: MultiSeriesConfig | undefined = this.config.multiSeries;
25
+
26
+ const categoryIndex = getDimensionHeaderIndex(analytics.headers ?? [], categoryDimension ?? "");
27
+ const seriesIndex = getDimensionHeaderIndex(analytics.headers ?? [], head(config.layout.series) ?? "");
28
+ const valueIndex = getDimensionHeaderIndex(analytics.headers ?? [], "value");
29
+
30
+ if (!categoryDimension) {
31
+ throw new Error("At least one category dimension is required");
32
+ }
33
+
34
+ if (!seriesConfig) {
35
+ throw new Error("MultiSeries config is required for chart type multi-series");
36
+ }
37
+
38
+ return compact(
39
+ seriesConfig?.series?.map(({ id, as, cumulative, yAxis }) => {
40
+ const dataItem = analytics.metaData?.items[id as any];
41
+ const categoryItems = analytics.metaData?.dimensions[categoryDimension as "dx" | "ou" | "pe"];
42
+
43
+ const data = categoryItems?.map((item: string) => {
44
+ const row = analytics.rows?.find((row: any) => row[categoryIndex] === item && row[seriesIndex] === id);
45
+ return row?.[valueIndex] ? parseFloat(row?.[valueIndex]) : 0;
46
+ });
47
+
48
+ let cumulativeData: number[] = [];
49
+
50
+ if (cumulative) {
51
+ cumulativeData =
52
+ data?.reduce((acc, curr, index) => {
53
+ if (index === 0) {
54
+ return [...acc, curr];
55
+ }
56
+ return [...acc, acc[index - 1] + curr];
57
+ }, [] as number[]) ?? [];
58
+ }
59
+
60
+ return {
61
+ name: dataItem?.name,
62
+ data: cumulative ? cumulativeData : data,
63
+ type: as,
64
+ yAxis: yAxis ?? 0,
65
+ };
66
+ })
67
+ ) as SeriesOptionsType[];
68
+ }
69
+
70
+ getXAxis(): XAxisOptions | undefined {
71
+ return {
72
+ type: "category",
73
+ categories: getAllCategories(this.analytics, this.config),
74
+ crosshair: true,
75
+ labels: {
76
+ enabled: true,
77
+ },
78
+ title: { text: "" },
79
+ };
80
+ }
81
+
82
+ getYAxis(): YAxisOptions[] {
83
+ let yAxes: YAxisOptions[] = [];
84
+
85
+ if (this.config.multiSeries?.yAxes) {
86
+ yAxes = this.config.multiSeries?.yAxes;
87
+ } else {
88
+ yAxes = super.getYAxis();
89
+ }
90
+
91
+ if (!this.config.multiSeries?.target) {
92
+ return yAxes;
93
+ }
94
+
95
+ const { value, styles, label } = this.config.multiSeries?.target ?? {};
96
+
97
+ return [
98
+ {
99
+ ...yAxes[0],
100
+ plotLines: [
101
+ ...(yAxes[0].plotLines ?? []),
102
+ {
103
+ color: styles?.color ?? "#00FF00",
104
+ dashStyle: styles?.dashStyle ?? "Solid",
105
+ value,
106
+ width: styles?.width ?? 2,
107
+ zIndex: styles?.zIndex ?? 1000,
108
+ label: label,
109
+ },
110
+ ],
111
+ },
112
+ ...yAxes.slice(1),
113
+ ];
114
+ }
115
+ }
@@ -0,0 +1,54 @@
1
+ import {PlotOptions, SeriesOptionsType, XAxisOptions} from "highcharts";
2
+ import {head} from "lodash";
3
+ import {DHIS2Chart} from "./index";
4
+
5
+ export class DHIS2PieChart extends DHIS2Chart {
6
+ getHighchartsType(): string {
7
+ return "pie";
8
+ }
9
+
10
+ getPlotOptions(): PlotOptions {
11
+ return {
12
+ pie: {
13
+ allowPointSelect: true,
14
+ cursor: "pointer",
15
+ dataLabels: {
16
+ enabled: true,
17
+ format: "<b>{point.name}</b>: {point.percentage:.1f} %",
18
+ },
19
+ },
20
+ };
21
+ }
22
+
23
+ getSeries(): SeriesOptionsType[] {
24
+ const analytics = this.analytics;
25
+ const config = this.config;
26
+ const seriesDimension = head(config.layout.series);
27
+ const seriesIndex = analytics?.headers?.findIndex((h) => h.name === seriesDimension) ?? -1;
28
+ const valueIndex = analytics?.headers?.findIndex((h) => h.name === "value") ?? -1;
29
+
30
+ if (!seriesDimension) {
31
+ throw new Error("Pie chart must have a series dimension");
32
+ }
33
+ const seriesValues = analytics.metaData?.dimensions?.[seriesDimension as "dx" | "ou" | "pe"];
34
+
35
+ return [
36
+ {
37
+ id: seriesDimension ?? "",
38
+ name: analytics.metaData?.items?.[seriesDimension as any]?.name ?? "",
39
+ data: seriesValues?.map((value: string) => {
40
+ const row = analytics?.rows?.find((row: any) => row[seriesIndex] === value);
41
+
42
+ return {
43
+ name: analytics.metaData?.items?.[value as any]?.name,
44
+ y: row?.[valueIndex] ? parseFloat(row?.[valueIndex] ?? "") : 0,
45
+ };
46
+ }),
47
+ } as SeriesOptionsType,
48
+ ];
49
+ }
50
+
51
+ getXAxis(): XAxisOptions | undefined {
52
+ return undefined;
53
+ }
54
+ }
@@ -0,0 +1,38 @@
1
+ import Highcharts from "highcharts";
2
+ import HighchartsReact from "highcharts-react-official";
3
+ import HighChartsExportCSV from "highcharts/modules/export-data";
4
+ import HighChartsExport from "highcharts/modules/exporting";
5
+ import HighChartsFullScreen from "highcharts/modules/full-screen";
6
+
7
+ export function setupHighchartsModules(highcharts: typeof Highcharts) {
8
+ HighChartsExport(highcharts);
9
+ HighChartsExportCSV(highcharts);
10
+ HighChartsFullScreen(highcharts);
11
+ }
12
+
13
+ export function onFullScreenView(chartRef: HighchartsReact.RefObject, options?: Highcharts.Options) {
14
+ chartRef?.chart?.fullscreen.toggle();
15
+ }
16
+
17
+ export function onPDFDownload(chartRef: HighchartsReact.RefObject, options?: Highcharts.Options) {
18
+ chartRef?.chart.exportChart({ type: "application/pdf" }, options ?? {});
19
+ }
20
+
21
+ export function onCSVDownload(chartRef: HighchartsReact.RefObject, options?: Highcharts.Options) {
22
+ chartRef?.chart.downloadCSV();
23
+ }
24
+
25
+ export const onImageDownload = (chartRef: HighchartsReact.RefObject, type: "png" | "svg+xml" | "jpeg", options?: Highcharts.Options) => {
26
+ chartRef?.chart.exportChart({ type: `image/${type}` }, options ?? {});
27
+ };
28
+
29
+ export const onViewAsTable = (chartRef: HighchartsReact.RefObject, show: boolean) => {
30
+ const options = chartRef?.chart.options;
31
+ chartRef?.chart?.update({
32
+ ...options,
33
+ exporting: {
34
+ ...options?.exporting,
35
+ showTable: show,
36
+ },
37
+ });
38
+ };
@@ -0,0 +1,48 @@
1
+
2
+ .highcharts-data-table table {
3
+ border: 1px solid rgb(232, 237, 242);
4
+ text-align: left;
5
+ min-width: 100%;
6
+ border-collapse: collapse;
7
+ vertical-align: top;
8
+ }
9
+
10
+ .highcharts-data-table td,
11
+ .highcharts-data-table th,
12
+ .highcharts-data-table caption {
13
+ font-size: 14px;
14
+ line-height: 16px;
15
+ padding: 8px 6px;
16
+ height: 32px;
17
+ border-bottom: 1px solid rgb(232, 237, 242);
18
+ }
19
+
20
+ .highcharts-data-table thead tr {
21
+ background: transparent;
22
+ }
23
+
24
+ .highcharts-data-table tr:nth-child(even) {
25
+ background: rgb(251, 252, 253);
26
+ }
27
+
28
+ .highcharts-data-table tr {
29
+ cursor: pointer;
30
+ }
31
+
32
+ .highcharts-data-table tr:hover {
33
+ background: rgb(251, 252, 253);
34
+ }
35
+
36
+ .highcharts-data-table caption {
37
+ border-bottom: none;
38
+ font-size: 1.1em;
39
+ font-weight: bold;
40
+ }
41
+
42
+ .highcharts-sort-ascending::after {
43
+ content: " ↓";
44
+ }
45
+
46
+ .highcharts-sort-descending::after {
47
+ content: " ↑";
48
+ }