diff --git a/cypress/component/ChartSelector/ChartSelector.cy.js b/cypress/component/ChartSelector/ChartSelector.cy.js index e90b7e6..13132b7 100644 --- a/cypress/component/ChartSelector/ChartSelector.cy.js +++ b/cypress/component/ChartSelector/ChartSelector.cy.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { ChartSelector } from '../../../src/Components/GrowthChartSelector'; import { useChartDataForGender } from '../../../src/utils/DataFetching/Sorting/useChartDataForGender'; +import { ChartSelector } from '../../../src/components/GrowthChart/GrowthChartSelector'; describe('ChartSelector', () => { const TestComponent = () => { diff --git a/cypress/component/ChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.cy.js b/cypress/component/ChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.cy.js index 9d33cc4..15a3046 100644 --- a/cypress/component/ChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.cy.js +++ b/cypress/component/ChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.cy.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { ChartSelectorDropdown } from '../../../../src/Components/GrowthChartSelector/ChartSelectorDropdown'; +import { ChartSelectorDropdown } from '../../../../src/components/GrowthChart/GrowthChartSelector/ChartSelectorDropdown'; describe('ChartSelectorDropdown', () => { const TestComponent = () => { diff --git a/i18n/en.pot b/i18n/en.pot index db01be5..f85f4a4 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-03-18T10:17:19.228Z\n" -"PO-Revision-Date: 2024-03-18T10:17:19.228Z\n" +"POT-Creation-Date: 2024-03-21T19:28:47.462Z\n" +"PO-Revision-Date: 2024-03-21T19:28:47.462Z\n" msgid "Date" msgstr "Date" @@ -17,18 +17,6 @@ msgstr "Height" msgid "Weight" msgstr "Weight" -msgid "Year" -msgstr "Year" - -msgid "Years" -msgstr "Years" - -msgid "Month" -msgstr "Month" - -msgid "Months" -msgstr "Months" - msgid "Growth Chart" msgstr "Growth Chart" @@ -38,20 +26,17 @@ msgstr "There was an error fetching the config for the growth chart." msgid "Please check the configuration in Datastore Management and try again." msgstr "Please check the configuration in Datastore Management and try again." +msgid "Months" +msgstr "Months" + msgid "Weeks" msgstr "Weeks" -msgid "Head circumference (cm)" -msgstr "Head circumference (cm)" - -msgid "Length (cm)" -msgstr "Length (cm)" - -msgid "Height (cm)" -msgstr "Height (cm)" +msgid "Head circumference" +msgstr "Head circumference" -msgid "Weight (kg)" -msgstr "Weight (kg)" +msgid "Length" +msgstr "Length" msgid "Head circumference for age" msgstr "Head circumference for age" @@ -82,3 +67,12 @@ msgstr "Boy" msgid "Girl" msgstr "Girl" + +msgid "Year" +msgstr "Year" + +msgid "Years" +msgstr "Years" + +msgid "Month" +msgstr "Month" diff --git a/src/Components/GrowthChart/GrowthChartBuilder/GrowthChartBuilder.tsx b/src/Components/GrowthChart/GrowthChartBuilder/GrowthChartBuilder.tsx deleted file mode 100644 index 040dc66..0000000 --- a/src/Components/GrowthChart/GrowthChartBuilder/GrowthChartBuilder.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React from 'react'; -import i18n from '@dhis2/d2-i18n'; -import { Line } from 'react-chartjs-2'; -import Chart, { ChartOptions } from 'chart.js/auto'; -import annotationPlugin from 'chartjs-plugin-annotation'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { ChartDataTypes, CategoryToLabel, CategoryLabels } from '../../../types/chartDataTypes'; -import { chartLineColorPicker } from '../../../utils/chartLineColorPicker'; -import { annotateLineEnd } from '../../../utils/annotateLineEnd'; -import { useMeasurementDataChart } from '../../../utils/useMeasurementDataChart'; - -interface GrowthChartBuilderProps extends ChartDataTypes { - category: keyof typeof CategoryToLabel; - dataset: string | number; - dateOfBirth: Date; -} - -export const GrowthChartBuilder = ({ - datasetValues, - datasetMetadata, - yAxisValues, - keysDataSet, - annotations, - measurementData, - category, - dataset, - dateOfBirth, -}: GrowthChartBuilderProps) => { - Chart.register(annotationPlugin); - - const { minDataValue, maxDataValue } = yAxisValues; - - const adjustIndex = (dataset === '2 to 5 years') ? 24 : 0; - - const ZscoreLines = keysDataSet.map((key) => ({ - data: datasetValues.map((entry, index) => ({ - x: (category !== 'wflh_b' && category !== 'wflh_g') ? adjustIndex + index : datasetMetadata.range.start + index, - y: entry[key], - })), - borderWidth: 0.9, - borderColor: chartLineColorPicker(key), - label: key, - })); - - const categoryLabel = CategoryToLabel[category]; - const datasetMappings: { [key: string]: string } = { - [CategoryLabels.hcfa]: 'headCircumference', - [CategoryLabels.lhfa]: 'height', - [CategoryLabels.wfa]: 'weight', - [CategoryLabels.wflh]: 'weight', - }; - - const fieldName = datasetMappings[categoryLabel]; - const formattedFieldName = fieldName.charAt(0).toUpperCase() + fieldName.substring(1); - const MeasurementData = useMeasurementDataChart(measurementData, fieldName, category, dataset, dateOfBirth); - - const data : any = { datasets: [...ZscoreLines, ...MeasurementData] }; - - const options: ChartOptions<'line'> = { - elements: { point: { radius: 0, hoverRadius: 0 } }, - plugins: { - annotation: { annotations }, - legend: { display: false }, - tooltip: { - enabled: true, - intersect: false, - position: 'nearest', - backgroundColor: 'white', - bodyFont: { size: 12 }, - bodyColor: 'black', - borderColor: 'black', - borderWidth: 1, - padding: 12, - caretPadding: 4, - boxPadding: 4, - usePointStyle: true, - filter: (tooltipItem: any) => tooltipItem.dataset.id === 'measurementData', - callbacks: { - title: () => '', - beforeLabel: (tooltipItem: any) => { - const date = new Date(tooltipItem.raw.eventDate).toLocaleDateString(); - return `${i18n.t('Date')}: ${date}`; - }, - label: (tooltipItem: any) => { - if (category === 'wflh_b' || category === 'wflh_g') { - return `${i18n.t('Height')}: ${tooltipItem.label} | ${i18n.t('Weight')}: ${tooltipItem.formattedValue}`; - } - const value = tooltipItem.formattedValue; - return `${formattedFieldName}: ${value}`; - }, - }, - }, - }, - scales: { - x: { - type: 'linear', - title: { - display: true, - text: i18n.t(datasetMetadata.xAxisLabel), - font: { size: 13 }, - }, - min: datasetMetadata.range.start, - max: datasetMetadata.range.end, - ticks: { stepSize: 1 }, - }, - y: { - title: { - display: true, - text: i18n.t(datasetMetadata.yAxisLabel), - font: { size: 13 }, - }, - position: 'left', - min: minDataValue, - max: maxDataValue, - }, - yRight: { - position: 'right', - min: minDataValue, - max: maxDataValue, - ticks: { padding: 18 }, - }, - }, - animation: { - onComplete: (chartAnimation: any) => annotateLineEnd(chartAnimation), - onProgress: (chartAnimation: any) => annotateLineEnd(chartAnimation), - }, - }; - - return ( -
- - {/* eslint-disable-next-line react/no-unused-prop-types */} - {({ height, width }: { height: number, width: number }) => ( -
- -
- )} -
-
- ); -}; diff --git a/src/Components/GrowthChart/GrowthChartOptions/index.ts b/src/Components/GrowthChart/GrowthChartOptions/index.ts deleted file mode 100644 index 4597951..0000000 --- a/src/Components/GrowthChart/GrowthChartOptions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { GrowthChartAnnotations } from './GrowthChartAnnotations'; -export type { AnnotationLabelType } from './GrowthChartAnnotations'; diff --git a/src/DataSets/WhoStandardDataSets/ChartDataZscores.ts b/src/DataSets/WhoStandardDataSets/ChartDataZscores.ts index 26264a5..9baed92 100644 --- a/src/DataSets/WhoStandardDataSets/ChartDataZscores.ts +++ b/src/DataSets/WhoStandardDataSets/ChartDataZscores.ts @@ -8,8 +8,8 @@ import { wfh_g_2_5_y_z, wfl_g_0_2_y_z } from './ZScores/wfhl-girls'; import { wfh_b_2_5_y_z, wfl_b_0_2_y_z } from './ZScores/wfhl-boys'; import { ChartCodes, CategoryCodes, - measurementTypeCodes, - timeUnitCodes, + MeasurementTypeCodesLabel, + TimeUnitCodes, ChartData, GenderCodes, CategoryLabels } from '../../types/chartDataTypes'; @@ -25,8 +25,8 @@ export const chartData: ChartData = { datasetValues: hcfa_b_0_13_w_z, metadata: { chartLabel: ChartCodes.hcfa_b_0_13_w_z, - xAxisLabel: timeUnitCodes.weeks, - yAxisLabel: measurementTypeCodes.hc_cm, + xAxisLabel: TimeUnitCodes.weeks, + yAxisLabel: MeasurementTypeCodesLabel.headCircumference, range: { start: 0, end: 13 }, }, }, @@ -34,8 +34,8 @@ export const chartData: ChartData = { datasetValues: hcfa_b_0_5_y_z, metadata: { chartLabel: ChartCodes.hcfa_b_0_5_y_z.label, - xAxisLabel: timeUnitCodes.months, - yAxisLabel: measurementTypeCodes.hc_cm, + xAxisLabel: TimeUnitCodes.months, + yAxisLabel: MeasurementTypeCodesLabel.headCircumference, range: { start: 0, end: 60 }, }, }, @@ -51,8 +51,8 @@ export const chartData: ChartData = { datasetValues: hcfa_g_0_13_w_z, metadata: { chartLabel: ChartCodes.hcfa_g_0_13_w_z, - xAxisLabel: timeUnitCodes.weeks, - yAxisLabel: measurementTypeCodes.hc_cm, + xAxisLabel: TimeUnitCodes.weeks, + yAxisLabel: MeasurementTypeCodesLabel.headCircumference, range: { start: 0, end: 13 }, }, }, @@ -60,8 +60,8 @@ export const chartData: ChartData = { datasetValues: hcfa_g_0_5_y_z, metadata: { chartLabel: ChartCodes.hcfa_g_0_5_y_z, - xAxisLabel: timeUnitCodes.months, - yAxisLabel: measurementTypeCodes.hc_cm, + xAxisLabel: TimeUnitCodes.months, + yAxisLabel: MeasurementTypeCodesLabel.headCircumference, range: { start: 0, end: 60 }, }, }, @@ -77,8 +77,8 @@ export const chartData: ChartData = { datasetValues: lhfa_b_0_13_w_z, metadata: { chartLabel: ChartCodes.lhfa_b_0_13_w_z, - xAxisLabel: timeUnitCodes.weeks, - yAxisLabel: measurementTypeCodes.l_cm, + xAxisLabel: TimeUnitCodes.weeks, + yAxisLabel: MeasurementTypeCodesLabel.length, range: { start: 0, end: 13 }, }, }, @@ -86,8 +86,8 @@ export const chartData: ChartData = { datasetValues: lhfa_b_0_2_y_z, metadata: { chartLabel: ChartCodes.lhfa_b_0_2_y_z, - xAxisLabel: timeUnitCodes.months, - yAxisLabel: measurementTypeCodes.l_cm, + xAxisLabel: TimeUnitCodes.months, + yAxisLabel: MeasurementTypeCodesLabel.length, range: { start: 0, end: 24 }, }, }, @@ -95,8 +95,8 @@ export const chartData: ChartData = { datasetValues: lhfa_b_2_5_y_z, metadata: { chartLabel: ChartCodes.lhfa_b_2_5_y_z, - xAxisLabel: timeUnitCodes.months, - yAxisLabel: measurementTypeCodes.h_cm, + xAxisLabel: TimeUnitCodes.months, + yAxisLabel: MeasurementTypeCodesLabel.height, range: { start: 24, end: 60 }, }, }, @@ -112,8 +112,8 @@ export const chartData: ChartData = { datasetValues: lhfa_g_0_13_w_z, metadata: { chartLabel: ChartCodes.lhfa_g_0_13_w_z, - xAxisLabel: timeUnitCodes.weeks, - yAxisLabel: measurementTypeCodes.l_cm, + xAxisLabel: TimeUnitCodes.weeks, + yAxisLabel: MeasurementTypeCodesLabel.length, range: { start: 0, end: 13 }, }, }, @@ -121,8 +121,8 @@ export const chartData: ChartData = { datasetValues: lhfa_g_0_2_y_z, metadata: { chartLabel: ChartCodes.lhfa_g_0_2_y_z.label, - xAxisLabel: timeUnitCodes.months, - yAxisLabel: measurementTypeCodes.l_cm, + xAxisLabel: TimeUnitCodes.months, + yAxisLabel: MeasurementTypeCodesLabel.length, range: { start: 0, end: 24 }, }, }, @@ -130,8 +130,8 @@ export const chartData: ChartData = { datasetValues: lhfa_g_2_5_y_z, metadata: { chartLabel: ChartCodes.lhfa_g_2_5_y_z, - xAxisLabel: timeUnitCodes.months, - yAxisLabel: measurementTypeCodes.h_cm, + xAxisLabel: TimeUnitCodes.months, + yAxisLabel: MeasurementTypeCodesLabel.height, range: { start: 24, end: 60 }, }, }, @@ -147,8 +147,8 @@ export const chartData: ChartData = { datasetValues: wfa_b_0_13_w_z, metadata: { chartLabel: ChartCodes.wfa_b_0_13_w_z, - xAxisLabel: timeUnitCodes.weeks, - yAxisLabel: measurementTypeCodes.w_kg, + xAxisLabel: TimeUnitCodes.weeks, + yAxisLabel: MeasurementTypeCodesLabel.weight, range: { start: 0, end: 13 }, }, }, @@ -156,8 +156,8 @@ export const chartData: ChartData = { datasetValues: wfa_b_0_5_y_z, metadata: { chartLabel: ChartCodes.wfa_b_0_5_y_z.label, - xAxisLabel: timeUnitCodes.months, - yAxisLabel: measurementTypeCodes.w_kg, + xAxisLabel: TimeUnitCodes.months, + yAxisLabel: MeasurementTypeCodesLabel.weight, range: { start: 0, end: 60 }, }, }, @@ -173,8 +173,8 @@ export const chartData: ChartData = { datasetValues: wfa_g_0_13_w_z, metadata: { chartLabel: ChartCodes.wfa_g_0_13_w_z, - xAxisLabel: timeUnitCodes.weeks, - yAxisLabel: measurementTypeCodes.w_kg, + xAxisLabel: TimeUnitCodes.weeks, + yAxisLabel: MeasurementTypeCodesLabel.weight, range: { start: 0, end: 13 }, }, }, @@ -182,8 +182,8 @@ export const chartData: ChartData = { datasetValues: wfa_g_0_5_y_z, metadata: { chartLabel: ChartCodes.wfa_g_0_5_y_z.label, - xAxisLabel: timeUnitCodes.months, - yAxisLabel: measurementTypeCodes.w_kg, + xAxisLabel: TimeUnitCodes.months, + yAxisLabel: MeasurementTypeCodesLabel.weight, range: { start: 0, end: 60 }, }, }, @@ -199,8 +199,8 @@ export const chartData: ChartData = { datasetValues: wfl_b_0_2_y_z, metadata: { chartLabel: ChartCodes.wfl_b_0_2_y_z.label, - xAxisLabel: measurementTypeCodes.l_cm, - yAxisLabel: measurementTypeCodes.w_kg, + xAxisLabel: MeasurementTypeCodesLabel.length, + yAxisLabel: MeasurementTypeCodesLabel.weight, range: { start: 45, end: 110 }, }, }, @@ -208,8 +208,8 @@ export const chartData: ChartData = { datasetValues: wfh_b_2_5_y_z, metadata: { chartLabel: ChartCodes.wfh_b_2_5_y_z, - xAxisLabel: measurementTypeCodes.h_cm, - yAxisLabel: measurementTypeCodes.w_kg, + xAxisLabel: MeasurementTypeCodesLabel.height, + yAxisLabel: MeasurementTypeCodesLabel.weight, range: { start: 65, end: 120 }, }, }, @@ -225,8 +225,8 @@ export const chartData: ChartData = { datasetValues: wfl_g_0_2_y_z, metadata: { chartLabel: ChartCodes.wfl_g_0_2_y_z.label, - xAxisLabel: measurementTypeCodes.l_cm, - yAxisLabel: measurementTypeCodes.w_kg, + xAxisLabel: MeasurementTypeCodesLabel.length, + yAxisLabel: MeasurementTypeCodesLabel.weight, range: { start: 45, end: 110 }, }, }, @@ -234,8 +234,8 @@ export const chartData: ChartData = { datasetValues: wfh_g_2_5_y_z, metadata: { chartLabel: ChartCodes.wfh_g_2_5_y_z, - xAxisLabel: measurementTypeCodes.h_cm, - yAxisLabel: measurementTypeCodes.w_kg, + xAxisLabel: MeasurementTypeCodesLabel.height, + yAxisLabel: MeasurementTypeCodesLabel.weight, range: { start: 65, end: 120 }, }, }, diff --git a/src/Plugin.tsx b/src/Plugin.tsx index 6b8eaf4..a7e5203 100644 --- a/src/Plugin.tsx +++ b/src/Plugin.tsx @@ -4,8 +4,8 @@ import './tailwind.css'; import './index.css'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import i18n from '@dhis2/d2-i18n'; -import { WidgetCollapsible } from './Components/WidgetCollapsible'; -import { GrowthChart } from './Components/GrowthChart/GrowthChart'; +import { WidgetCollapsible } from './components/WidgetCollapsible'; +import { GrowthChart } from './components/GrowthChart/GrowthChart'; import { EnrollmentOverviewProps } from './Plugin.types'; import { useTeiById } from './utils/DataFetching/Hooks'; import { useChartConfig } from './utils/DataFetching/Hooks/useChartConfig'; diff --git a/src/UI/GenericError/ChartConfigError.tsx b/src/UI/GenericError/ChartConfigError.tsx index aaf819f..a500262 100644 --- a/src/UI/GenericError/ChartConfigError.tsx +++ b/src/UI/GenericError/ChartConfigError.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import i18n from '@dhis2/d2-i18n'; -import { WidgetCollapsible } from '../../Components/WidgetCollapsible'; +import { WidgetCollapsible } from '../../components/WidgetCollapsible'; import { Warning } from '../Icons'; export const ChartConfigError = () => { diff --git a/src/UI/GenericLoading/GenericLoading.tsx b/src/UI/GenericLoading/GenericLoading.tsx index 6e56a7c..44134c3 100644 --- a/src/UI/GenericLoading/GenericLoading.tsx +++ b/src/UI/GenericLoading/GenericLoading.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import i18n from '@dhis2/d2-i18n'; -import { WidgetCollapsible } from '../../Components/WidgetCollapsible'; +import { WidgetCollapsible } from '../../components/WidgetCollapsible'; export const GenericLoading = (): JSX.Element => { const [open, setOpen] = useState(true); diff --git a/src/Components/GrowthChart/ChartSettingsButton/ChartSettingsButton.tsx b/src/components/GrowthChart/ChartSettingsButton/ChartSettingsButton.tsx similarity index 94% rename from src/Components/GrowthChart/ChartSettingsButton/ChartSettingsButton.tsx rename to src/components/GrowthChart/ChartSettingsButton/ChartSettingsButton.tsx index 99044e4..ae44320 100644 --- a/src/Components/GrowthChart/ChartSettingsButton/ChartSettingsButton.tsx +++ b/src/components/GrowthChart/ChartSettingsButton/ChartSettingsButton.tsx @@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { usePopper } from 'react-popper'; import { EllipsisButton } from './EllipsisButton'; import { PopoverList, PopoverListItem, PopoverListDivider } from './PopoverList'; -import { usePrintDocument } from '../../../utils/usePrintDocument'; +import { PrintDocument } from '../../../utils/ChartOptions'; import { CategoryCodes, ChartData } from '../../../types/chartDataTypes'; import { PdfIcon } from '../../../UI/Icons/PdfIcon'; @@ -42,7 +42,7 @@ export const ChartSettingsButton = ({ category, dataset, gender }: ChartSettings }; }, []); - const handlePrintDocument = () => usePrintDocument({ + const handlePrintDocument = () => PrintDocument({ category, dataset, gender, }); diff --git a/src/Components/GrowthChart/ChartSettingsButton/EllipsisButton/EllipsisButton.tsx b/src/components/GrowthChart/ChartSettingsButton/EllipsisButton/EllipsisButton.tsx similarity index 100% rename from src/Components/GrowthChart/ChartSettingsButton/EllipsisButton/EllipsisButton.tsx rename to src/components/GrowthChart/ChartSettingsButton/EllipsisButton/EllipsisButton.tsx diff --git a/src/Components/GrowthChart/ChartSettingsButton/EllipsisButton/index.ts b/src/components/GrowthChart/ChartSettingsButton/EllipsisButton/index.ts similarity index 100% rename from src/Components/GrowthChart/ChartSettingsButton/EllipsisButton/index.ts rename to src/components/GrowthChart/ChartSettingsButton/EllipsisButton/index.ts diff --git a/src/Components/GrowthChart/ChartSettingsButton/PopoverList/PopoverList.tsx b/src/components/GrowthChart/ChartSettingsButton/PopoverList/PopoverList.tsx similarity index 100% rename from src/Components/GrowthChart/ChartSettingsButton/PopoverList/PopoverList.tsx rename to src/components/GrowthChart/ChartSettingsButton/PopoverList/PopoverList.tsx diff --git a/src/Components/GrowthChart/ChartSettingsButton/PopoverList/PopoverListDivider.tsx b/src/components/GrowthChart/ChartSettingsButton/PopoverList/PopoverListDivider.tsx similarity index 100% rename from src/Components/GrowthChart/ChartSettingsButton/PopoverList/PopoverListDivider.tsx rename to src/components/GrowthChart/ChartSettingsButton/PopoverList/PopoverListDivider.tsx diff --git a/src/Components/GrowthChart/ChartSettingsButton/PopoverList/PopoverListItem.tsx b/src/components/GrowthChart/ChartSettingsButton/PopoverList/PopoverListItem.tsx similarity index 100% rename from src/Components/GrowthChart/ChartSettingsButton/PopoverList/PopoverListItem.tsx rename to src/components/GrowthChart/ChartSettingsButton/PopoverList/PopoverListItem.tsx diff --git a/src/Components/GrowthChart/ChartSettingsButton/PopoverList/index.ts b/src/components/GrowthChart/ChartSettingsButton/PopoverList/index.ts similarity index 100% rename from src/Components/GrowthChart/ChartSettingsButton/PopoverList/index.ts rename to src/components/GrowthChart/ChartSettingsButton/PopoverList/index.ts diff --git a/src/Components/GrowthChart/ChartSettingsButton/index.ts b/src/components/GrowthChart/ChartSettingsButton/index.ts similarity index 100% rename from src/Components/GrowthChart/ChartSettingsButton/index.ts rename to src/components/GrowthChart/ChartSettingsButton/index.ts diff --git a/src/Components/GrowthChart/GrowthChart.tsx b/src/components/GrowthChart/GrowthChart.tsx similarity index 79% rename from src/Components/GrowthChart/GrowthChart.tsx rename to src/components/GrowthChart/GrowthChart.tsx index 5af70d6..5f3512a 100644 --- a/src/Components/GrowthChart/GrowthChart.tsx +++ b/src/components/GrowthChart/GrowthChart.tsx @@ -1,10 +1,8 @@ import React, { useEffect, useMemo, useState } from 'react'; import { GrowthChartBuilder } from './GrowthChartBuilder'; -import { useRangeTimePeriod } from '../../utils/useRangeTimePeriod'; -import { ChartSelector } from '../GrowthChartSelector'; -import { ChartData, GenderCodes, CategoryCodes, MeasurementData } from '../../types/chartDataTypes'; -import { useCalculateMinMaxValues } from '../../utils/useCalculateMinMaxValues'; -import { GrowthChartAnnotations } from './GrowthChartOptions'; +import { ChartSelector } from './GrowthChartSelector'; +import { GenderCodes, CategoryCodes, MeasurementData } from '../../types/chartDataTypes'; +import { useCalculateMinMaxValues } from '../../utils/Hooks/Calculations/useCalculateMinMaxValues'; import { ChartSettingsButton } from './ChartSettingsButton'; import { useChartDataForGender } from '../../utils/DataFetching/Sorting/useChartDataForGender'; import { MappedEntityValues } from '../../utils/DataFetching/Sorting/useMappedTrackedEntity'; @@ -23,13 +21,13 @@ export const GrowthChart = ({ const { chartDataForGender } = useChartDataForGender({ gender }); const [category, setCategory] = useState(); - const [dataset, setDataset] = useState(); + const [dataset, setDataset] = useState(); useEffect(() => { if (Object.keys(chartDataForGender).length > 0) { const newCategory = Object.keys(chartDataForGender)[0] as keyof typeof CategoryCodes; setCategory(newCategory); - const newDataset = Object.keys(chartDataForGender[newCategory].datasets)[0] as keyof ChartData; + const newDataset = Object.keys(chartDataForGender[newCategory].datasets)[0]; setDataset(newDataset); } }, [chartDataForGender]); @@ -40,11 +38,8 @@ export const GrowthChart = ({ const dataSetEntry = chartDataForGender[category]?.datasets[dataset]; - const dataSetValues = dataSetEntry?.datasetValues; const dataSetMetadata = dataSetEntry?.metadata; - - const xAxisValues = useRangeTimePeriod(dataSetMetadata?.range.start, dataSetMetadata?.range.end); - + const dataSetValues = dataSetEntry?.datasetValues; const { min, max } = useCalculateMinMaxValues(dataSetValues); const [minDataValue, maxDataValue] = useMemo(() => { @@ -53,16 +48,10 @@ export const GrowthChart = ({ return [minVal, maxVal]; }, [min, max]); - const annotations = GrowthChartAnnotations(xAxisValues, dataSetMetadata?.xAxisLabel); - if (!chartDataForGender || !dataSetValues) { return null; } - if (xAxisValues.length !== dataSetValues.length) { - console.error('xAxisValues and dataSet should have the same length'); - } - const keysDataSet = Object.keys(dataSetValues[0]); const yAxisValues = { minDataValue, maxDataValue }; @@ -91,13 +80,11 @@ export const GrowthChart = ({ >; + backgroundColor: string; + bodyFont: { size: number }; + bodyColor: string; + borderColor: string; + borderWidth: number; + padding: number; + caretPadding: number; + boxPadding: number; + usePointStyle: boolean; + filter: (tooltipItem: any) => boolean; + callbacks: { + title: () => string; + beforeLabel: (tooltipItem: any) => string; + label: (tooltipItem: any) => string; + }; +} + +export const ChartTooltipConfig = (formattedFieldName: string, category: string): TooltipConfig => ({ + enabled: true, + intersect: false, + position: 'nearest' as Scriptable>, + backgroundColor: 'white', + bodyFont: { size: 12 }, + bodyColor: 'black', + borderColor: 'black', + borderWidth: 1, + padding: 12, + caretPadding: 4, + boxPadding: 4, + usePointStyle: true, + filter: (tooltipItem) => tooltipItem.dataset.id === 'measurementData', + callbacks: { + title: () => '', + beforeLabel: (tooltipItem) => { + const date = new Date(tooltipItem.raw.eventDate).toLocaleDateString(); + return `${i18n.t('Date')}: ${date}`; + }, + label: (tooltipItem) => { + if (category === 'wflh_b' || category === 'wflh_g') { + return `${i18n.t('Height')}: ${tooltipItem.label} | ${i18n.t('Weight')}: ${tooltipItem.formattedValue}`; + } + const value = tooltipItem.formattedValue; + return `${formattedFieldName}: ${value}`; + }, + }, +}); diff --git a/src/components/GrowthChart/GrowthChartBuilder/GrowthChartBuilder.tsx b/src/components/GrowthChart/GrowthChartBuilder/GrowthChartBuilder.tsx new file mode 100644 index 0000000..261aa9d --- /dev/null +++ b/src/components/GrowthChart/GrowthChartBuilder/GrowthChartBuilder.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { Line } from 'react-chartjs-2'; +import Chart, { ChartOptions } from 'chart.js/auto'; +import annotationPlugin from 'chartjs-plugin-annotation'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { ChartDataTypes, CategoryToLabel, MeasurementTypeCodesLabel, + MeasurementTypeCodes, DataSetLabels, CategoryCodes } from '../../../types/chartDataTypes'; +import { GrowthChartAnnotations, AnnotateLineEnd } from '../../../utils/ChartOptions'; +import { useMeasurementPlotting, useZscoreLines } from '../../../utils/Hooks/ChartDataVisualization'; +import { ChartTooltipConfig } from './ChartTooltipConfig'; + +interface GrowthChartBuilderProps extends ChartDataTypes { + category: keyof typeof CategoryToLabel; + dataset: string; + dateOfBirth: Date; +} + +export const GrowthChartBuilder = ({ + datasetValues, + datasetMetadata, + yAxisValues, + keysDataSet, + measurementData, + category, + dataset, + dateOfBirth, +}: GrowthChartBuilderProps) => { + Chart.register(annotationPlugin); + + const { minDataValue, maxDataValue } = yAxisValues; + + const categoryLabel = CategoryToLabel[category]; + + const MeasuremenCode = MeasurementTypeCodes[category]; + const MeasuremenLabel = MeasurementTypeCodesLabel[MeasuremenCode]; + + const adjustIndex = (dataset === DataSetLabels.y_2_5) ? 24 : 0; + const startIndex = (category !== CategoryCodes.wflh_b && category !== CategoryCodes.wflh_g) ? adjustIndex : datasetMetadata.range.start; + + const ZscoreLinesData = useZscoreLines(datasetValues, keysDataSet, datasetMetadata, category, dataset, startIndex); + const MeasurementData = useMeasurementPlotting(measurementData, MeasuremenCode, category, dataset, dateOfBirth, startIndex); + const data: any = { datasets: [...ZscoreLinesData, ...MeasurementData] }; + const annotations = GrowthChartAnnotations(ZscoreLinesData, datasetMetadata); + + const options: ChartOptions<'line'> = { + elements: { point: { radius: 0, hoverRadius: 0 } }, + plugins: { + annotation: { annotations }, + legend: { display: false }, + tooltip: ChartTooltipConfig(MeasuremenLabel, categoryLabel), + }, + scales: { + x: { + type: 'linear', + title: { + display: true, + text: i18n.t(datasetMetadata.xAxisLabel), + font: { size: 13 }, + }, + min: datasetMetadata.range.start, + max: datasetMetadata.range.end, + ticks: { stepSize: 1 }, + }, + y: { + title: { + display: true, + text: i18n.t(datasetMetadata.yAxisLabel), + font: { size: 13 }, + }, + position: 'left', + min: minDataValue, + max: maxDataValue, + }, + yRight: { + position: 'right', + min: minDataValue, + max: maxDataValue, + ticks: { padding: 18 }, + }, + }, + animation: { + onComplete: (chartAnimation: any) => AnnotateLineEnd(chartAnimation), + onProgress: (chartAnimation: any) => AnnotateLineEnd(chartAnimation), + }, + }; + + return ( +
+ + {/* eslint-disable-next-line react/no-unused-prop-types */} + {({ height, width }: { height: number, width: number }) => ( +
+ +
+ )} +
+
+ ); +}; diff --git a/src/Components/GrowthChart/GrowthChartBuilder/index.ts b/src/components/GrowthChart/GrowthChartBuilder/index.ts similarity index 100% rename from src/Components/GrowthChart/GrowthChartBuilder/index.ts rename to src/components/GrowthChart/GrowthChartBuilder/index.ts diff --git a/src/Components/GrowthChartSelector/ChartSelector.tsx b/src/components/GrowthChart/GrowthChartSelector/ChartSelector.tsx similarity index 92% rename from src/Components/GrowthChartSelector/ChartSelector.tsx rename to src/components/GrowthChart/GrowthChartSelector/ChartSelector.tsx index 956f3c0..89e39a6 100644 --- a/src/Components/GrowthChartSelector/ChartSelector.tsx +++ b/src/components/GrowthChart/GrowthChartSelector/ChartSelector.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { ChartData, CategoryCodes, GenderCodes, CategoryToLabel } from '../../types/chartDataTypes'; +import { ChartData, CategoryCodes, GenderCodes, CategoryToLabel } from '../../../types/chartDataTypes'; import { ChartSelectorDropdown } from './ChartSelectorDropdown/ChartSelectorDropdown'; interface ChartSelectorProps { category: keyof typeof CategoryCodes; dataset: keyof ChartData; setCategory: (category: keyof typeof CategoryCodes) => void; - setDataset: (dataset: keyof ChartData) => void; + setDataset: (dataset: string) => void; chartData: ChartData; isDisabled?: boolean; gender: string; @@ -26,11 +26,11 @@ export const ChartSelector = ({ const handleCategoryChange = (value: string) => { const newCategory = Object.keys(chartData).find((key) => chartData[key].categoryMetadata.label === value) as keyof typeof CategoryCodes; setCategory(newCategory); - setDataset(Object.keys(chartData[newCategory].datasets)[0] as keyof ChartData); + setDataset(Object.keys(chartData[newCategory].datasets)[0]); }; const handleDatasetChange = (value: string) => { - setDataset(value as keyof ChartData); + setDataset(value); }; return ( diff --git a/src/Components/GrowthChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.tsx b/src/components/GrowthChart/GrowthChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.tsx similarity index 95% rename from src/Components/GrowthChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.tsx rename to src/components/GrowthChart/GrowthChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.tsx index 8791eff..7318fcb 100644 --- a/src/Components/GrowthChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.tsx +++ b/src/components/GrowthChart/GrowthChartSelector/ChartSelectorDropdown/ChartSelectorDropdown.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Menu } from '@headlessui/react'; -import { CategoryCodes, ChartData } from '../../../types/chartDataTypes'; -import { Chevron } from '../../../UI/Icons'; +import { CategoryCodes, ChartData } from '../../../../types/chartDataTypes'; +import { Chevron } from '../../../../UI/Icons'; interface ChartSelectorDropdownProps { title: keyof typeof CategoryCodes | keyof ChartData; diff --git a/src/Components/GrowthChartSelector/ChartSelectorDropdown/index.ts b/src/components/GrowthChart/GrowthChartSelector/ChartSelectorDropdown/index.ts similarity index 100% rename from src/Components/GrowthChartSelector/ChartSelectorDropdown/index.ts rename to src/components/GrowthChart/GrowthChartSelector/ChartSelectorDropdown/index.ts diff --git a/src/Components/GrowthChartSelector/index.ts b/src/components/GrowthChart/GrowthChartSelector/index.ts similarity index 100% rename from src/Components/GrowthChartSelector/index.ts rename to src/components/GrowthChart/GrowthChartSelector/index.ts diff --git a/src/Components/WidgetCollapsible/IconButton/IconButton.tsx b/src/components/WidgetCollapsible/IconButton/IconButton.tsx similarity index 100% rename from src/Components/WidgetCollapsible/IconButton/IconButton.tsx rename to src/components/WidgetCollapsible/IconButton/IconButton.tsx diff --git a/src/Components/WidgetCollapsible/IconButton/index.ts b/src/components/WidgetCollapsible/IconButton/index.ts similarity index 100% rename from src/Components/WidgetCollapsible/IconButton/index.ts rename to src/components/WidgetCollapsible/IconButton/index.ts diff --git a/src/Components/WidgetCollapsible/WidgetCollapsible.tsx b/src/components/WidgetCollapsible/WidgetCollapsible.tsx similarity index 100% rename from src/Components/WidgetCollapsible/WidgetCollapsible.tsx rename to src/components/WidgetCollapsible/WidgetCollapsible.tsx diff --git a/src/Components/WidgetCollapsible/index.ts b/src/components/WidgetCollapsible/index.ts similarity index 100% rename from src/Components/WidgetCollapsible/index.ts rename to src/components/WidgetCollapsible/index.ts diff --git a/src/types/chartDataTypes.ts b/src/types/chartDataTypes.ts index d79a920..a15b87b 100644 --- a/src/types/chartDataTypes.ts +++ b/src/types/chartDataTypes.ts @@ -1,5 +1,4 @@ import i18n from '@dhis2/d2-i18n'; -import { AnnotationLabelType } from '../Components/GrowthChart/GrowthChartOptions'; export interface MeasurementData { eventDate: string; @@ -18,10 +17,8 @@ export interface ChartDataTypes { xAxisLabel: string; range: { start: number; end: number }; }; - xAxisValues: number[]; yAxisValues: { minDataValue: number; maxDataValue: number }; keysDataSet: string[]; - annotations: AnnotationLabelType[]; measurementData: MeasurementData[]; } @@ -31,29 +28,41 @@ export interface ChartData { label: string; gender: string; }; - datasets: { [key: string]: { - datasetValues: { [key: string]: number }[]; - metadata: { - chartLabel: string; - yAxisLabel: string; - xAxisLabel: string; - range: { start: number; end: number }; + datasets: { + [key: string]: { + datasetValues: { [key: string]: number }[]; + metadata: { + chartLabel: string; + yAxisLabel: string; + xAxisLabel: string; + range: { start: number; end: number }; }; } }; }; } -export const timeUnitCodes = Object.freeze({ +export const TimeUnitCodes = Object.freeze({ months: i18n.t('Months'), weeks: i18n.t('Weeks'), }); -export const measurementTypeCodes = Object.freeze({ - hc_cm: i18n.t('Head circumference (cm)'), - l_cm: i18n.t('Length (cm)'), - h_cm: i18n.t('Height (cm)'), - w_kg: i18n.t('Weight (kg)'), +export const MeasurementTypeCodesLabel = Object.freeze({ + headCircumference: i18n.t('Head circumference'), + length: i18n.t('Length'), + height: i18n.t('Height'), + weight: i18n.t('Weight'), +}); + +export const MeasurementTypeCodes = Object.freeze({ + hcfa_b: 'headCircumference', + hcfa_g: 'headCircumference', + lhfa_b: 'height', + lhfa_g: 'height', + wfa_g: 'weight', + wfa_b: 'weight', + wflh_b: 'weight', + wflh_g: 'weight', }); export const CategoryLabels = Object.freeze({ @@ -84,7 +93,6 @@ export const CategoryToLabel = Object.freeze({ wflh_b: CategoryLabels.wflh, wflh_g: CategoryLabels.wflh, }); - export const DataSetLabels = Object.freeze({ y_0_5: i18n.t('0 to 5 years'), w_0_13: i18n.t('0 to 13 weeks'), diff --git a/src/utils/annotateLineEnd.ts b/src/utils/ChartOptions/AnnotateLineEnd.ts similarity index 95% rename from src/utils/annotateLineEnd.ts rename to src/utils/ChartOptions/AnnotateLineEnd.ts index 720de8f..af837b9 100644 --- a/src/utils/annotateLineEnd.ts +++ b/src/utils/ChartOptions/AnnotateLineEnd.ts @@ -7,7 +7,7 @@ interface DataSet { label: string; } -export const annotateLineEnd = (animation: Animation & { chart?: Chart }) => { +export const AnnotateLineEnd = (animation: Animation & { chart?: Chart }) => { const { chart } = animation; if (!chart) return; diff --git a/src/utils/chartLineColorPicker.ts b/src/utils/ChartOptions/ChartLineColorPicker.ts similarity index 84% rename from src/utils/chartLineColorPicker.ts rename to src/utils/ChartOptions/ChartLineColorPicker.ts index ff629d7..58c5ca9 100644 --- a/src/utils/chartLineColorPicker.ts +++ b/src/utils/ChartOptions/ChartLineColorPicker.ts @@ -1,4 +1,4 @@ -export const chartLineColorPicker = (key: string): string => { +export const ChartLineColorPicker = (key: string): string => { switch (key) { case 'SD3neg': return 'black'; diff --git a/src/Components/GrowthChart/GrowthChartOptions/GrowthChartAnnotations.tsx b/src/utils/ChartOptions/GrowthChartAnnotations.tsx similarity index 69% rename from src/Components/GrowthChart/GrowthChartOptions/GrowthChartAnnotations.tsx rename to src/utils/ChartOptions/GrowthChartAnnotations.tsx index 4846ae9..1d2dab2 100644 --- a/src/Components/GrowthChart/GrowthChartOptions/GrowthChartAnnotations.tsx +++ b/src/utils/ChartOptions/GrowthChartAnnotations.tsx @@ -1,11 +1,12 @@ import i18n from '@dhis2/d2-i18n'; -import { timeUnitCodes } from '../../../types/chartDataTypes'; +import { TimeUnitCodes } from '../../types/chartDataTypes'; interface TimeUnitData { singular: string; plural: string; divisor: number; } + export interface AnnotationLabelType { display: boolean; content?: (value: number) => string; @@ -14,12 +15,12 @@ export interface AnnotationLabelType { } const timeUnitData: { [key: string]: TimeUnitData } = { - [timeUnitCodes.months]: { + [TimeUnitCodes.months]: { singular: i18n.t('Year'), plural: i18n.t('Years'), divisor: 12, }, - [timeUnitCodes.weeks]: { + [TimeUnitCodes.weeks]: { singular: i18n.t('Month'), plural: i18n.t('Months'), divisor: 4, @@ -31,25 +32,28 @@ const contentText = (value: number, xAxisLabel: string) => { return `${value} ${value === 1 ? singular : plural}`; }; -export const GrowthChartAnnotations = (xAxisValues: number[], xAxisLabel: string) => { - const timeUnitConfig = timeUnitData[xAxisLabel]; +export const GrowthChartAnnotations = ( + ZscoreLines: any[], + datasetMetadata: any, +): AnnotationLabelType[] => { + const timeUnitConfig = timeUnitData[datasetMetadata.xAxisLabel]; if (timeUnitConfig) { - const firstXValue = xAxisValues[0]; + const xValues = ZscoreLines[0]?.data.map((entry: any) => entry.x) || []; + const { divisor } = timeUnitConfig; - const annotations = xAxisValues - .filter((label) => label % divisor === 0) - .map((label) => ({ + const annotations = xValues.filter((label: number) => label % divisor === 0) + .map((label: number) => ({ display: true, type: 'line', scaleID: 'x', borderWidth: 1.2, - value: label - firstXValue, + value: label, label: { display: true, content: () => { const value = label / divisor; - return contentText(value, xAxisLabel); + return contentText(value, datasetMetadata.xAxisLabel); }, position: 'end', yAdjust: 10, @@ -58,7 +62,7 @@ export const GrowthChartAnnotations = (xAxisValues: number[], xAxisLabel: string backgroundColor: 'rgba(237, 237, 237)', }, })); - if ((xAxisValues.length - 1) % 12 === 0) { + if ((xValues.length - 1) % 12 === 0) { annotations.pop(); } annotations.shift(); diff --git a/src/utils/usePrintDocument.ts b/src/utils/ChartOptions/PrintDocument.ts similarity index 84% rename from src/utils/usePrintDocument.ts rename to src/utils/ChartOptions/PrintDocument.ts index 176b57d..538f980 100644 --- a/src/utils/usePrintDocument.ts +++ b/src/utils/ChartOptions/PrintDocument.ts @@ -1,6 +1,6 @@ import html2canvas from 'html2canvas'; import JsPDF from 'jspdf'; -import { CategoryCodes, ChartData, CategoryToLabel } from '../types/chartDataTypes'; +import { CategoryCodes, ChartData, CategoryToLabel } from '../../types/chartDataTypes'; interface PrintDocumentProps { category: keyof typeof CategoryCodes; @@ -8,7 +8,7 @@ interface PrintDocumentProps { gender: string; } -export const usePrintDocument = ({ category, dataset, gender }: PrintDocumentProps) => { +export const PrintDocument = ({ category, dataset, gender }: PrintDocumentProps) => { const datasetPdfLabel = String(dataset).replace(/ /g, '_'); const input = document.getElementById('divToPrint'); html2canvas(input, { logging: false }) diff --git a/src/utils/ChartOptions/index.ts b/src/utils/ChartOptions/index.ts new file mode 100644 index 0000000..3fe2c1e --- /dev/null +++ b/src/utils/ChartOptions/index.ts @@ -0,0 +1,4 @@ +export { GrowthChartAnnotations } from './GrowthChartAnnotations'; +export { PrintDocument } from './PrintDocument'; +export { ChartLineColorPicker } from './ChartLineColorPicker'; +export { AnnotateLineEnd } from './AnnotateLineEnd'; diff --git a/src/utils/DataFetching/Sorting/useMappedGrowthVariables.ts b/src/utils/DataFetching/Sorting/useMappedGrowthVariables.ts index b838947..658a646 100644 --- a/src/utils/DataFetching/Sorting/useMappedGrowthVariables.ts +++ b/src/utils/DataFetching/Sorting/useMappedGrowthVariables.ts @@ -19,24 +19,28 @@ export const useMappedGrowthVariables = ({ events, growthVariables, isWeightInGrams, -}: UseMappedGrowthVariablesProps): MappedDataValue[] | undefined => events?.map((event: Event) => { - const dataValueMap: { weight: string; headCircumference: string; height: string; [key: string]: string } = { - weight: '', - headCircumference: '', - height: '', - }; +}: UseMappedGrowthVariablesProps): MappedDataValue[] | undefined => { + const mappedData = events?.map((event: Event) => { + const dataValueMap: { weight: string; headCircumference: string; height: string; [key: string]: string } = { + weight: '', + headCircumference: '', + height: '', + }; + + if (growthVariables && event.dataValues) { + Object.entries(growthVariables).reduce((acc, [key, value]: [string, string]) => { + const dataValue = String(Object.entries(event.dataValues).find(([dataElement]) => dataElement === value)?.[1]); + if (dataValue && value) { + acc[key] = (key === 'weight' && (isWeightInGrams || Number(dataValue) > 1000)) ? String(Number(dataValue) / 1000) : dataValue; + } + return acc; + }, dataValueMap); + } - if (growthVariables && event.dataValues) { - Object.entries(growthVariables).reduce((acc, [key, value]: [string, string]) => { - const dataValue = String(Object.entries(event.dataValues).find(([dataElement]) => dataElement === value)?.[1]); - if (dataValue && value) { - acc[key] = (key === 'weight' && (isWeightInGrams || Number(dataValue) > 1000)) ? String(Number(dataValue) / 1000) : dataValue; - } - return acc; - }, dataValueMap); - } + const eventDate = String(event.occurredAt).split('T')[0]; - const eventDate = String(event.occurredAt).split('T')[0]; + return { eventDate, dataValues: dataValueMap }; + }); - return { eventDate, dataValues: dataValueMap }; -}); + return mappedData?.sort((a, b) => a.eventDate.localeCompare(b.eventDate)); +}; diff --git a/src/utils/Hooks/Calculations/index.ts b/src/utils/Hooks/Calculations/index.ts new file mode 100644 index 0000000..3edf8ec --- /dev/null +++ b/src/utils/Hooks/Calculations/index.ts @@ -0,0 +1,2 @@ +export { useCalculateDecimalDate } from './useCalculateDecimalDate'; +export { useCalculateMinMaxValues } from './useCalculateMinMaxValues'; diff --git a/src/utils/Hooks/Calculations/useCalculateDecimalDate.ts b/src/utils/Hooks/Calculations/useCalculateDecimalDate.ts new file mode 100644 index 0000000..cf284cf --- /dev/null +++ b/src/utils/Hooks/Calculations/useCalculateDecimalDate.ts @@ -0,0 +1,32 @@ +import { DataSetLabels } from '../../../types/chartDataTypes'; + +interface DatasetMap { + [x: string]: () => string; +} + +export const useCalculateDecimalDate = (date: string, dataset: string, dateOfBirth: Date): string => { + const millisecondsInDay = 1000 * 60 * 60 * 24; + const formattedDate: Date = new Date(date); + const diffInMilliseconds = formattedDate.getTime() - dateOfBirth.getTime(); + + const calculateDiffInMonths = (maxMonths: number | null = null): string => { + const millisecondsInMonth = millisecondsInDay * 30.44; + const diffInMonths = diffInMilliseconds / millisecondsInMonth; + if (diffInMonths < 0 || (maxMonths !== null && diffInMonths > maxMonths)) return null; + return diffInMonths.toFixed(2); + }; + + const datasetMap: DatasetMap = { + [DataSetLabels.w_0_13]: () => { + const millisecondsInWeek = millisecondsInDay * 7; + const diffInWeeks = diffInMilliseconds / millisecondsInWeek; + if (diffInWeeks < 0 || diffInWeeks > 13) return null; + return diffInWeeks.toFixed(2); + }, + [DataSetLabels.y_0_2]: () => calculateDiffInMonths(24), + [DataSetLabels.y_0_5]: () => calculateDiffInMonths(60), + [DataSetLabels.y_2_5]: () => calculateDiffInMonths(60), + }; + + return datasetMap[dataset]?.() ?? null; +}; diff --git a/src/utils/useCalculateMinMaxValues.ts b/src/utils/Hooks/Calculations/useCalculateMinMaxValues.ts similarity index 87% rename from src/utils/useCalculateMinMaxValues.ts rename to src/utils/Hooks/Calculations/useCalculateMinMaxValues.ts index 27e222a..535ec6f 100644 --- a/src/utils/useCalculateMinMaxValues.ts +++ b/src/utils/Hooks/Calculations/useCalculateMinMaxValues.ts @@ -2,7 +2,7 @@ export function useCalculateMinMaxValues(datasetValues: Array) { if (!datasetValues) { return { min: 0, max: 0 }; } - const flatValues: number[] = datasetValues.flatMap((entry: any) => Object.values(entry)) as number[]; + const flatValues: number[] = datasetValues.flatMap((entry: any) => (Object.values(entry)) as number[]); return { min: Math.min(...flatValues), max: Math.max(...flatValues), diff --git a/src/utils/Hooks/ChartDataVisualization/index.ts b/src/utils/Hooks/ChartDataVisualization/index.ts new file mode 100644 index 0000000..f2a99f9 --- /dev/null +++ b/src/utils/Hooks/ChartDataVisualization/index.ts @@ -0,0 +1,2 @@ +export { useZscoreLines } from './useZscoreLines'; +export { useMeasurementPlotting } from './useMeasurementPlotting'; diff --git a/src/utils/Hooks/ChartDataVisualization/useMeasurementPlotting.ts b/src/utils/Hooks/ChartDataVisualization/useMeasurementPlotting.ts new file mode 100644 index 0000000..858f92b --- /dev/null +++ b/src/utils/Hooks/ChartDataVisualization/useMeasurementPlotting.ts @@ -0,0 +1,68 @@ +import { useCalculateDecimalDate } from '../Calculations/useCalculateDecimalDate'; +import { DataSetLabels, CategoryCodes } from '../../../types/chartDataTypes'; + +export interface MeasurementDataEntry { + eventDate: string | Date; + dataValues: { + [key: string]: number | string; + }; +} + +export const useMeasurementPlotting = ( + measurementData: MeasurementDataEntry[] | undefined, + fieldName: string, + category: string, + dataset: string, + dateOfBirth: Date, + startIndex: number, +) => { + const measurementDataValues: { x: Date | number | string; y: number; eventDate?: Date }[] = []; + + if (!measurementData) { + return []; + } + + const processEntry = (entry: MeasurementDataEntry) => { + let xValue: Date | number | string; + let yValue: number; + + if (category === CategoryCodes.wflh_b || category === CategoryCodes.wflh_g) { + xValue = parseFloat(String(entry.dataValues.height)); + yValue = parseFloat(String(entry.dataValues.weight)); + } else { + const dateString: string = typeof entry.eventDate === 'string' ? entry.eventDate : entry.eventDate.toISOString(); + const xValueDecimalDate: string = useCalculateDecimalDate(dateString, dataset, dateOfBirth); + xValue = xValueDecimalDate; + yValue = parseFloat(String(entry.dataValues[fieldName])); + } + + const eventDateValue = new Date(entry.eventDate); + measurementDataValues.push({ + x: xValue, + y: yValue, + eventDate: eventDateValue, + }); + }; + + const validDatasets = Object.values(DataSetLabels); + if (validDatasets.includes(dataset)) { + measurementData.forEach(processEntry); + + if (dataset !== DataSetLabels.y_2_5) { + measurementDataValues.filter((data) => typeof data.x === 'number' && data.x >= startIndex); + } + } + + return [ + { + id: 'measurementData', + data: measurementDataValues, + borderWidth: 1.5, + borderColor: 'rgba(43,102,147,255)', + pointRadius: 3, + pointBackgroundColor: 'rgba(43,102,147,255)', + fill: false, + borderDash: [5, 5], + }, + ]; +}; diff --git a/src/utils/Hooks/ChartDataVisualization/useZscoreLines.ts b/src/utils/Hooks/ChartDataVisualization/useZscoreLines.ts new file mode 100644 index 0000000..4528a10 --- /dev/null +++ b/src/utils/Hooks/ChartDataVisualization/useZscoreLines.ts @@ -0,0 +1,33 @@ +import { useEffect, useState } from 'react'; +import { ChartLineColorPicker } from '../../ChartOptions'; + +interface DatasetValues { + [key: string]: number; +} + +export const useZscoreLines = ( + datasetValues: DatasetValues[], + keysDataSet: string[], + datasetMetadata: any, + category: string, + dataset: string | number, + startIndex: number, +) => { + const [zScoreLines, setZScoreLines] = useState([]); + + useEffect(() => { + const ZscoreLines = keysDataSet.map((key) => ({ + data: datasetValues.map((entry, index) => ({ + x: startIndex + index, + y: entry[key], + })), + borderWidth: 0.9, + borderColor: ChartLineColorPicker(key), + label: key, + })); + + setZScoreLines(ZscoreLines); + }, [datasetValues, keysDataSet, datasetMetadata, category, dataset, startIndex]); + + return zScoreLines; +}; diff --git a/src/utils/buildURLQueryString.ts b/src/utils/buildURLQueryString.ts deleted file mode 100644 index 4b9561c..0000000 --- a/src/utils/buildURLQueryString.ts +++ /dev/null @@ -1,13 +0,0 @@ -const LOCALE_EN = 'en'; - -export const buildUrlQueryString = (queryArgs: Record) => - Object - .entries(queryArgs) - .filter(([, value]) => value != null) - .sort(([keyA], [keyB]) => keyA.localeCompare(keyB, LOCALE_EN)) - .reduce((searchParams, [key, value]) => { - // $FlowFixMe - value && searchParams.append(key, value); - return searchParams; - }, new URLSearchParams()) - .toString(); diff --git a/src/utils/useCalculateDecimalDate.ts b/src/utils/useCalculateDecimalDate.ts deleted file mode 100644 index 5675910..0000000 --- a/src/utils/useCalculateDecimalDate.ts +++ /dev/null @@ -1,44 +0,0 @@ -export const useCalculateDecimalDate = (date: string, dataset: string, dateOfBirth: Date): string => { - const millisecondsInDay = 1000 * 60 * 60 * 24; - - if (dataset === '0 to 13 weeks') { - const millisecondsInWeek = millisecondsInDay * 7; - - const formattedDate: Date = new Date(date); - const diffInMilliseconds = formattedDate.getTime() - dateOfBirth.getTime(); - const diffInWeeks = (diffInMilliseconds / millisecondsInWeek); - - if (diffInWeeks < 0) return null; - if (diffInWeeks > 13) return null; - return diffInWeeks.toFixed(2); - } - - if (dataset === '0 to 2 years' || dataset === '0 to 5 years') { - const millisecondsInMonth = millisecondsInDay * 30.44; - - const formattedDate: Date = new Date(date); - const diffInMilliseconds = formattedDate.getTime() - dateOfBirth.getTime(); - const diffInMonths = (diffInMilliseconds / millisecondsInMonth); - - if (diffInMonths < 0) return null; - - if (dataset === '0 to 2 years' && diffInMonths > 24) return null; - if (dataset === '0 to 5 years' && diffInMonths > 60) return null; - - return diffInMonths.toFixed(2); - } - - if (dataset === '2 to 5 years') { - const millisecondsInMonth = millisecondsInDay * 30.44; - - const formattedDate: Date = new Date(date); - const diffInMilliseconds = formattedDate.getTime() - dateOfBirth.getTime(); - const diffInMonths = (diffInMilliseconds / millisecondsInMonth); - - if (diffInMonths < 24) return null; - if (diffInMonths > 60) return null; - return diffInMonths.toFixed(2); - } - - return null; -}; diff --git a/src/utils/useMeasurementDataChart.ts b/src/utils/useMeasurementDataChart.ts deleted file mode 100644 index f501df0..0000000 --- a/src/utils/useMeasurementDataChart.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { useCalculateDecimalDate } from './useCalculateDecimalDate'; - -export interface MeasurementDataEntry { - eventDate: string | Date; - dataValues: { - [key: string]: number | string; - }; -} - -export const useMeasurementDataChart = ( - measurementData: MeasurementDataEntry[], - fieldName: string, - category: string, - dataset: string | number, - dateOfBirth: Date, -) => { - const measurementDataValues: { x: Date | number | string; y: number; eventDate?: Date }[] = []; - - if (dataset === '0 to 13 weeks' || dataset === '0 to 2 years' || dataset === '0 to 5 years') { - measurementData?.forEach((entry: MeasurementDataEntry) => { - let xValue: Date | number | string; - let yValue: number; - - if (category === 'wflh_b' || category === 'wflh_g') { - xValue = parseFloat(entry.dataValues.height as string); - yValue = parseFloat(entry.dataValues.weight as string); - } else { - let dateString: string; - if (typeof entry.eventDate === 'string') { - dateString = entry.eventDate; - } else { - dateString = entry.eventDate.toISOString(); - } - const xValueDecimalDate: string = useCalculateDecimalDate(dateString, dataset, dateOfBirth); - xValue = xValueDecimalDate; - yValue = parseFloat(entry.dataValues[fieldName] as string); - } - const eventDateValue = new Date(entry.eventDate); - return measurementDataValues.push({ - x: xValue, - y: yValue, - eventDate: eventDateValue, - }); - }); - } - - if (dataset === '2 to 5 years') { - measurementData = measurementData.slice(24); - - measurementData.forEach((entry: MeasurementDataEntry) => { - let xValue: Date | number | string; - let yValue: number; - - if (category === 'wflh_b' || category === 'wflh_g') { - xValue = parseFloat(entry.dataValues.height as string); - yValue = parseFloat(entry.dataValues.weight as string); - } else { - let dateString: string; - if (typeof entry.eventDate === 'string') { - dateString = entry.eventDate; - } else { - dateString = entry.eventDate.toISOString(); - } - const xValueDecimalDate: string = useCalculateDecimalDate(dateString, dataset, dateOfBirth); - xValue = xValueDecimalDate; - yValue = parseFloat(entry.dataValues[fieldName] as string); - } - const eventDateValue = new Date(entry.eventDate); - return measurementDataValues.push({ - x: xValue, - y: yValue, - eventDate: eventDateValue, - }); - }); - } - - return [ - { - id: 'measurementData', - data: measurementDataValues, - borderWidth: 1.5, - borderColor: 'rgba(43,102,147,255)', - pointRadius: 3, - pointBackgroundColor: 'rgba(43,102,147,255)', - fill: false, - borderDash: [5, 5], - }, - ]; -}; diff --git a/src/utils/useRangeTimePeriod.ts b/src/utils/useRangeTimePeriod.ts deleted file mode 100644 index 68fb5dd..0000000 --- a/src/utils/useRangeTimePeriod.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const useRangeTimePeriod = (start: number, end: number) => Array.from( - { length: end - start + 1 }, - (_, index) => start + index, -);