Skip to content
Open
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
12 changes: 12 additions & 0 deletions cypress/elements/event_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,16 @@ export class EventLayer extends Layer {

return this
}

selectIncludeUnclassifiedEvents() {
cy.contains('Include unclassified events').click()

return this
}

selectIncludeNoDataEvents() {
cy.contains('Include events with no data').click()

return this
}
}
6 changes: 6 additions & 0 deletions cypress/elements/thematic_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ export class ThematicLayer extends Layer {
return this
}

selectIncludeUnclassifiedOU() {
cy.contains('Include unclassified org units').click()

return this
}

selectIncludeNoDataOU() {
cy.contains('Include org units with no data').click()

Expand Down
5 changes: 4 additions & 1 deletion cypress/integration/layers/eventlayer.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const programE2E = {
name: 'E2E program',
stage: 'Stage 1 - Repeatable',
de: 'E2E - Yes/no',
options: ['Yes', 'No', 'Other'],
options: ['Yes', 'No', 'Unclassified', 'No data'],
}

const programIP = {
Expand Down Expand Up @@ -141,6 +141,9 @@ context('Event Layers', () => {
.contains(programE2E.de)
.click()

Layer.selectIncludeUnclassifiedEvents()
Layer.selectIncludeNoDataEvents()

Layer.addToMap()

Layer.validateDialogClosed(true)
Expand Down
11 changes: 7 additions & 4 deletions cypress/integration/layers/thematiclayer.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ context('Thematic Layers', () => {

// Examples of bubble labels:
// "10.5"
// "No data"
const bubbleLabelTextPattern = /^(\d+(\.\d+)?|No data)$/
// "No data (2)"
const bubbleLabelTextPattern = /^(\d+(\.\d+)?|No data)(\s*\(\d+\))?$/

// Choropleth
Layer.openDialog('Thematic')
Expand Down Expand Up @@ -177,7 +177,9 @@ context('Thematic Layers', () => {

cy.getByDataTest('dhis2-uicore-checkbox').eq(1).click()

Layer.openOu('Tonkolili').selectOu('Gbonkonlenken').addToMap()
Layer.openOu('Tonkolili').selectOu('Gbonkonlenken')

Layer.selectTab('Style').selectIncludeUnclassifiedOU().addToMap()

getMaps().click('center')

Expand Down Expand Up @@ -282,7 +284,8 @@ context('Thematic Layers', () => {
n: 7,
removeAll: false,
})
.addToMap()

Layer.selectTab('Style').selectIncludeUnclassifiedOU().addToMap()

Layer.validateDialogClosed(true)

Expand Down
65 changes: 40 additions & 25 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-04-27T11:12:19.709Z\n"
"PO-Revision-Date: 2026-04-27T11:12:19.709Z\n"
"POT-Creation-Date: 2026-04-28T15:36:36.629Z\n"
"PO-Revision-Date: 2026-04-28T15:36:36.629Z\n"

msgid "2020"
msgstr "2020"
Expand Down Expand Up @@ -44,18 +44,30 @@ msgstr "Auto"
msgid "Decimal places"
msgstr "Decimal places"

msgid "Legend set"
msgstr "Legend set"
msgid "Isolated class"
msgstr "Isolated class"

msgid "Min"
msgstr "Min"

msgid "Max"
msgstr "Max"

msgid "Max should be greater than min"
msgstr "Max should be greater than min"

msgid "Color"
msgstr "Color"

msgid "Size"
msgstr "Size"

msgid "Name"
msgstr "Name"

msgid "Legend set"
msgstr "Legend set"

msgid "Size"
msgstr "Size"

msgid "Name and value"
msgstr "Name and value"

Expand Down Expand Up @@ -101,6 +113,12 @@ msgstr "Enrollment > event > tracked entity > org unit coordinate"
msgid "Event > org unit coordinate"
msgstr "Event > org unit coordinate"

msgid "Include unclassified events"
msgstr "Include unclassified events"

msgid "Include events with no data"
msgstr "Include events with no data"

msgid "Previously selected value not available in list: {{id}}"
msgstr "Previously selected value not available in list: {{id}}"

Expand Down Expand Up @@ -356,18 +374,9 @@ msgstr "Min value is required"
msgid "Max value is required"
msgstr "Max value is required"

msgid "Max should be greater than min"
msgstr "Max should be greater than min"

msgid "Valid classes are {{minSteps}} to {{maxSteps}}"
msgstr "Valid classes are {{minSteps}} to {{maxSteps}}"

msgid "Min"
msgstr "Min"

msgid "Max"
msgstr "Max"

msgid "Facility buffer"
msgstr "Facility buffer"

Expand Down Expand Up @@ -401,6 +410,9 @@ msgstr "No organisation units are selected."
msgid "No legend set is selected"
msgstr "No legend set is selected"

msgid "Isolated class max should be greater than min"
msgstr "Isolated class max should be greater than min"

msgid "Event status"
msgstr "Event status"

Expand Down Expand Up @@ -446,15 +458,24 @@ msgstr "Polygons are represented by their centroids."
msgid "Labels"
msgstr "Labels"

msgid "Include org units with no data"
msgstr "Include org units with no data"

msgid "No data"
msgstr "No data"

msgid "Include unclassified org units"
msgstr "Include unclassified org units"

msgid "Unclassified"
msgstr "Unclassified"

msgid "Aggregation type"
msgstr "Aggregation type"

msgid "Only show completed events"
msgstr "Only show completed events"

msgid "Include org units with no data"
msgstr "Include org units with no data"

msgid "Low radius"
msgstr "Low radius"

Expand Down Expand Up @@ -717,9 +738,6 @@ msgstr "Groups"
msgid "Parent unit"
msgstr "Parent unit"

msgid "No data"
msgstr "No data"

msgid "Not set"
msgstr "Not set"

Expand Down Expand Up @@ -1816,9 +1834,6 @@ msgstr "Org units"
msgid "Facility"
msgstr "Facility"

msgid "Other"
msgstr "Other"

msgid "Start date is invalid"
msgstr "Start date is invalid"

Expand Down
17 changes: 13 additions & 4 deletions src/actions/layerEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ export const setLegendDecimalPlaces = (legendDecimalPlaces) => ({
legendDecimalPlaces,
})

export const setLegendIsolated = (legendIsolated) => ({
type: types.LAYER_EDIT_LEGEND_ISOLATED_SET,
legendIsolated,
})

// Set event status
export const setEventStatus = (status) => ({
type: types.LAYER_EDIT_EVENT_STATUS_SET,
Expand Down Expand Up @@ -352,10 +357,14 @@ export const setRenderingStrategy = (display) => ({
payload: display,
})

// Set no data color
export const setNoDataColor = (color) => ({
type: types.LAYER_EDIT_NO_DATA_COLOR_SET,
payload: color,
export const setNoDataLegend = (noDataLegend) => ({
type: types.LAYER_EDIT_NO_DATA_LEGEND_SET,
payload: noDataLegend,
})

export const setUnclassifiedLegend = (unclassifiedLegend) => ({
type: types.LAYER_EDIT_UNCLASSIFIED_LEGEND_SET,
payload: unclassifiedLegend,
})

// Set period for EE layer
Expand Down
2 changes: 2 additions & 0 deletions src/components/classification/Classification.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '../../util/colors.js'
import { SelectField, ColorScaleSelect } from '../core/index.js'
import DecimalPlacesSelect from './DecimalPlacesSelect.jsx'
import IsolatedClass from './IsolatedClass.jsx'
import styles from './styles/Classification.module.css'

const classRange = range(3, 10).map((num) => ({
Expand Down Expand Up @@ -73,6 +74,7 @@ const Classification = ({
width={190}
className={styles.scale}
/>
<IsolatedClass />
</div>,
]
}
Expand Down
104 changes: 104 additions & 0 deletions src/components/classification/IsolatedClass.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import i18n from '@dhis2/d2-i18n'
import { Help } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React, { useRef } from 'react'
import { connect } from 'react-redux'
import { setLegendIsolated } from '../../actions/layerEdit.js'
import { NO_DATA_COLOR } from '../../constants/layers.js'
import { Checkbox, ColorPicker, NumberField, TextField } from '../core/index.js'
import styles from './styles/Classification.module.css'

export const isValidIsolatedClass = ({ min, max } = {}) =>
min === undefined || max === undefined || min <= max

const DEFAULT_ISOLATED = { min: 0, max: 0, color: NO_DATA_COLOR }

const IsolatedClass = ({ legendIsolated, setLegendIsolated }) => {
const lastValue = useRef(null)

const onCheckboxChange = (checked) => {
if (checked) {
setLegendIsolated(lastValue.current ?? DEFAULT_ISOLATED)
} else {
lastValue.current = legendIsolated
setLegendIsolated(undefined)
}
}

return (
<>
<Checkbox
label={i18n.t('Isolated class')}
checked={legendIsolated !== undefined}
onChange={onCheckboxChange}
/>
{legendIsolated !== undefined && (
<div className={styles.isolatedRows}>
<div className={styles.isolatedRow}>
<NumberField
label={i18n.t('Min')}
value={legendIsolated.min}
onChange={(min) =>
setLegendIsolated({ ...legendIsolated, min })
}
className={styles.isolatedField}
/>
<NumberField
label={i18n.t('Max')}
value={legendIsolated.max}
onChange={(max) =>
setLegendIsolated({ ...legendIsolated, max })
}
className={styles.isolatedField}
/>
</div>
{!isValidIsolatedClass(legendIsolated) && (
<Help warning>
{i18n.t('Max should be greater than min')}
</Help>
)}
<div className={styles.isolatedRow}>
<ColorPicker
label={i18n.t('Color')}
color={legendIsolated.color || NO_DATA_COLOR}
onChange={(color) =>
setLegendIsolated({ ...legendIsolated, color })
}
width={50}
className={styles.isolatedColor}
/>
<TextField
label={i18n.t('Name')}
value={legendIsolated.name || ''}
placeholder={`${
legendIsolated.min ?? i18n.t('Min')
} - ${legendIsolated.max ?? i18n.t('Max')}`}
onChange={(name) =>
setLegendIsolated({
...legendIsolated,
name: name || undefined,
})
}
className={styles.isolatedName}
/>
</div>
</div>
)}
</>
)
}

IsolatedClass.propTypes = {
setLegendIsolated: PropTypes.func.isRequired,
legendIsolated: PropTypes.shape({
color: PropTypes.string,
max: PropTypes.number,
min: PropTypes.number,
name: PropTypes.string,
}),
}

export default connect(
({ layerEdit }) => ({ legendIsolated: layerEdit.legendIsolated }),
{ setLegendIsolated }
)(IsolatedClass)
6 changes: 5 additions & 1 deletion src/components/classification/NumericLegendStyle.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
CLASSIFICATION_SINGLE_COLOR,
} from '../../constants/layers.js'
import Classification from './Classification.jsx'
import IsolatedClass from './IsolatedClass.jsx'
import LegendSetSelect from './LegendSetSelect.jsx'
import LegendTypeSelect from './LegendTypeSelect.jsx'
import SingleColor from './SingleColor.jsx'
Expand Down Expand Up @@ -46,7 +47,10 @@ const NumericLegendStyle = (props) => {
dataItem={dataItem}
/>
{isSingleColor ? (
<SingleColor />
<>
<SingleColor />
<IsolatedClass />
</>
) : isPredefined ? (
<LegendSetSelect
legendSetError={legendSetError}
Expand Down
Loading
Loading