Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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: 2026-09-01T12:42:29.454Z\n"
"PO-Revision-Date: 2026-09-01T12:42:29.454Z\n"
"POT-Creation-Date: 2026-09-10T12:26:25.386Z\n"
"PO-Revision-Date: 2026-09-10T12:26:25.386Z\n"

msgid "2020"
msgstr "2020"
Expand Down Expand Up @@ -546,6 +546,15 @@ msgstr "Selection is initialized from shared periods"
msgid "Choose periods for all split layers"
msgstr "Choose periods for all split layers"

msgid "Donut"
msgstr "Donut"

msgid "Bar"
msgstr "Bar"

msgid "Select at least one data item on the Data tab"
msgstr "Select at least one data item on the Data tab"

msgid "Data is required"
msgstr "Data is required"

Expand Down Expand Up @@ -1738,6 +1747,9 @@ msgstr "Choropleth"
msgid "Bubble map"
msgstr "Bubble map"

msgid "Chart map"
msgstr "Chart map"

msgid "Automatic color legend"
msgstr "Automatic color legend"

Expand Down Expand Up @@ -1879,6 +1891,19 @@ msgstr "Data item was not found"
msgid "Thematic layer"
msgstr "Thematic layer"

msgid ""
"Chart maps are hard to read with more than {{max}} series ({{count}} "
"selected)"
msgid_plural ""
"Chart maps are hard to read with more than {{max}} series ({{count}} "
"selected)"
msgstr[0] ""
"Chart maps are hard to read with more than {{max}} series ({{count}} "
"selected)"
msgstr[1] ""
"Chart maps are hard to read with more than {{max}} series ({{count}} "
"selected)"

msgid "related"
msgstr "related"

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"html-to-image": "^1.11.13",
"lodash": "^4.18.1",
"loglevel": "^1.9.2",
"maplibre-gl": "^5.24.0",
"prop-types": "^15.8.1",
"query-string": "^9.5.1",
"react-redux": "^9.3.0",
Expand Down
Binary file added public/images/chart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/actions/layerEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ export const setDataItem = (dataItem, dimension) => ({
dimension,
})

// Set chart series data items (thematic chart map) — mockup for DHIS2-21461
export const setChartDataItems = (dataItems) => ({
type: types.LAYER_EDIT_CHART_DATA_ITEMS_SET,
dataItems,
})

// Set chart type: donut or bar (thematic chart map)
export const setChartType = (chartType) => ({
type: types.LAYER_EDIT_CHART_TYPE_SET,
chartType,
})

// Set data element used for styling (event)
export const setStyleDataItem = (dataItem) => ({
type: types.LAYER_EDIT_STYLE_DATA_ITEM_SET,
Expand Down
214 changes: 158 additions & 56 deletions src/components/edit/thematic/ThematicDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { useDispatch, useSelector } from 'react-redux'
import {
setClassification,
setDataItem,
setChartDataItems,
setChartType,
setLegendSet,
setNoDataLegend,
setUnclassifiedLegend,
Expand All @@ -25,6 +27,10 @@ import {
RENDERING_STRATEGY_SINGLE,
RENDERING_STRATEGY_TIMELINE,
RENDERING_STRATEGY_SPLIT_BY_PERIOD,
THEMATIC_CHART,
THEMATIC_CHART_MAX_SERIES,
CHART_TYPE_DONUT,
CHART_TYPE_BAR,
} from '../../../constants/layers.js'
import {
PREDEFINED_PERIODS,
Expand All @@ -35,9 +41,11 @@ import useLayersPeriodSync from '../../../hooks/useLayersPeriodSync.js'
import usePrevious from '../../../hooks/usePrevious.js'
import {
getDataItemFromColumns,
getDataItemsFromColumns,
getPeriodsFromFilters,
getDimensionsFromFilters,
} from '../../../util/analytics.js'
import { getChartSeriesColors } from '../../../util/colors.js'
import NumericLegendStyle from '../../classification/NumericLegendStyle.jsx'
import { Tab, Tabs, Checkbox } from '../../core/index.js'
import DimensionFilter from '../../dimensions/DimensionFilter.jsx'
Expand Down Expand Up @@ -79,6 +87,7 @@ const ThematicDialog = ({
radiusHigh,
method,
thematicMapType,
chartType,
legendIsolated,
}) => {
const dispatch = useDispatch()
Expand Down Expand Up @@ -107,7 +116,12 @@ const ThematicDialog = ({
const prevEndDate = usePrevious(endDate)
const prevValidateLayer = usePrevious(validateLayer)

const isChartMap = thematicMapType === THEMATIC_CHART
const dataItem = useMemo(() => getDataItemFromColumns(columns), [columns])
const chartDataItems = useMemo(
() => getDataItemsFromColumns(columns),
[columns]
)
const periods = useMemo(() => getPeriodsFromFilters(filters), [filters])
const dimensions = useMemo(
() => getDimensionsFromFilters(filters),
Expand Down Expand Up @@ -346,39 +360,61 @@ const ThematicDialog = ({
{tab === 'data' && (
<div data-test="thematicdialog-datatab">
<div className={styles.flexRowFlow}>
<DataDimension
displayNameProp={
currentUser.keyAnalysisDisplayProperty
}
selectedDimensions={
dataItem
? [
{
...dataItem,
type: dataItem.dimensionItemType,
},
]
: []
}
onSelect={async ({ items }) => {
const selected = items.at(-1) ?? {}
const legendSet = await fetchLegendSet(
selected
)
dispatch(
setDataItem(
{ ...selected, legendSet },
selected.type
{isChartMap ? (
<DataDimension
displayNameProp={
currentUser.keyAnalysisDisplayProperty
}
selectedDimensions={chartDataItems.map(
(item) => ({
...item,
type: item.dimensionItemType,
})
)}
onSelect={({ items }) =>
dispatch(setChartDataItems(items))
}
height="408px"
heightCalculation="375px"
maxSelections={THEMATIC_CHART_MAX_SERIES}
/>
) : (
<DataDimension
displayNameProp={
currentUser.keyAnalysisDisplayProperty
}
selectedDimensions={
dataItem
? [
{
...dataItem,
type: dataItem.dimensionItemType,
},
]
: []
}
onSelect={async ({ items }) => {
const selected = items.at(-1) ?? {}
const legendSet = await fetchLegendSet(
selected
)
)
}}
onCalculationSave={(items) =>
dispatch(setDataItem(items, items.type))
}
height="408px"
heightCalculation="375px"
maxSelections={1}
/>
dispatch(
setDataItem(
{ ...selected, legendSet },
selected.type
)
)
}}
onCalculationSave={(items) =>
dispatch(
setDataItem(items, items.type)
)
}
height="408px"
heightCalculation="375px"
maxSelections={1}
/>
)}
</div>

<div className={styles.flexColumnFlow}>
Expand Down Expand Up @@ -538,14 +574,42 @@ const ThematicDialog = ({
>
<div className={styles.flexColumn}>
<ThematicMapTypeSelect type={thematicMapType} />
<div
className={cx(
styles.flexInnerColumnFlow,
styles.radiusSelect
)}
>
<RadiusSelect className={styles.numberField} />
</div>
{isChartMap ? (
<div
className={styles.flexInnerColumnFlow}
data-test="thematicdialog-charttype"
>
<SegmentedControl
options={[
{
label: i18n.t('Donut'),
value: CHART_TYPE_DONUT,
},
{
label: i18n.t('Bar'),
value: CHART_TYPE_BAR,
},
]}
selected={
chartType || CHART_TYPE_DONUT
}
onChange={({ value }) =>
dispatch(setChartType(value))
}
/>
</div>
) : (
<div
className={cx(
styles.flexInnerColumnFlow,
styles.radiusSelect
)}
>
<RadiusSelect
className={styles.numberField}
/>
</div>
)}
<Labels includeDisplayOption />
<Checkbox
label={i18n.t(
Expand All @@ -562,22 +626,59 @@ const ThematicDialog = ({
/>
</div>
<div className={styles.flexColumn}>
<NumericLegendStyle
mapType={thematicMapType}
dataItem={dataItem}
legendSetError={errors.legendSetError}
className={styles.select}
/>
<UnclassifiedLegend
value={unclassifiedLegend}
onChange={(v) =>
dispatch(setUnclassifiedLegend(v))
}
/>
<NoDataLegend
value={noDataLegend}
onChange={(v) => dispatch(setNoDataLegend(v))}
/>
{isChartMap ? (
<div data-test="thematicdialog-chartseries">
{chartDataItems.length === 0 && (
<div className={styles.error}>
<IconErrorFilled24 />
{errors.dataError ||
i18n.t(
'Select at least one data item on the Data tab'
)}
</div>
)}
{getChartSeriesColors(
chartDataItems.length
).map((color, index) => (
<div
key={chartDataItems[index].id}
className={styles.flexInnerColumnFlow}
>
<span
style={{
display: 'inline-block',
width: 12,
height: 12,
borderRadius: '50%',
backgroundColor: color,
}}
/>
{chartDataItems[index].name}
</div>
))}
</div>
) : (
<>
<NumericLegendStyle
mapType={thematicMapType}
dataItem={dataItem}
legendSetError={errors.legendSetError}
className={styles.select}
/>
<UnclassifiedLegend
value={unclassifiedLegend}
onChange={(v) =>
dispatch(setUnclassifiedLegend(v))
}
/>
<NoDataLegend
value={noDataLegend}
onChange={(v) =>
dispatch(setNoDataLegend(v))
}
/>
</>
)}
</div>
</div>
)}
Expand All @@ -588,6 +689,7 @@ const ThematicDialog = ({

ThematicDialog.propTypes = {
backupPeriodsDates: PropTypes.object,
chartType: PropTypes.string,
columns: PropTypes.array,
currentUser: PropTypes.object,
endDate: PropTypes.string,
Expand Down
Loading
Loading