Skip to content

feat: new classifications and legend improvements [DHIS2-18242]#3637

Draft
BRaimbault wants to merge 32 commits into
masterfrom
feat/DHIS2-21142
Draft

feat: new classifications and legend improvements [DHIS2-18242]#3637
BRaimbault wants to merge 32 commits into
masterfrom
feat/DHIS2-21142

Conversation

@BRaimbault
Copy link
Copy Markdown
Collaborator

@BRaimbault BRaimbault commented Mar 20, 2026

Description last updated with: 9039317

Implements

  • DHIS2-21356: Saving event layer without choosing predefined legend set breaks layer
  • DHIS2-21142: Add new classification method (natural breaks, pretty breaks, log, sd)
  • DHIS2-20818: Thematic layer, bubble style - legends shows NaN when the all mapped values are equal
  • DHIS2-20287: Hide or show map layers while on the dashboard like it is the charts
  • DHIS2-19984: Values not displayed for data item with type NUMBER and associated to an option set in Maps app data table
  • DHIS2-19983: Improve ranges/legend sorting in Maps data table
  • DHIS2-19982: Chevron icon is not centered in the color select button
  • DHIS2-19850: Thematic / Facility / OrgUnits / Earth Engine layer - Deal with OrgUnits without coordinates
  • DHIS2-19812: Properly distinguish values outside of legend and no data and allow user to display both independently
  • DHIS2-19203: Use "Classes" instead of "Steps"
  • DHIS2-19078: Save visibility toggle state of layers and visibility and opacity of basemap with map
  • DHIS2-18963: Digit groups separator functionality in maps
  • DHIS2-15695: Improve display of value ranges in automatic legends
  • DHIS2-15514: Isolate a single value (eg. 0) in Maps
  • DHIS2-12860: Equal counts distribution is not ideal when many values are the same
  • DHIS2-10823: Improve the predefined legend display
  • DHIS2-8478: Automatic legends with few data points
  • DHIS2-3156: Allow setting of numeric precision in automatic map legends
  • DHIS2-237: Deal with events without coordinates

Description

DHIS2-21356: Saving event layer without choosing predefined legend set breaks layer

JIRA ticket: DHIS2-21356

Test map:

Details
  • Changes:

When switching to predefined legend type, automatically select the data item's associated legend set by default (or the first available one if none is associated), so saving without manually picking a legend set no longer breaks the layer

  • Code:
  • Move legend set auto-selection into LegendSetSelect, where the available list is guaranteed to be loaded: src/components/classification/LegendSetSelect.jsx
  • Remove the premature useEffect auto-selection from NumericLegendStyle; pass defaultLegendSet={dataItem?.legendSet} down to LegendSetSelect instead: src/components/classification/NumericLegendStyle.jsx

DHIS2-21142: Add new classification method (natural breaks, pretty breaks, log, sd)

JIRA ticket: DHIS2-21142

Test map: KKmdCyobwV8

Details

+ Changes:

  • Get natural breaks (ckmeans) and standard deviation from simple-statistics
  • Implement logarithmic and pretty breaks classifications
  • For natural breaks, implement two variants:
    • "intervals": legend without gaps
    • "clusters": legend can have gaps — easier to read with significant outliers or a small number of distinct values

+ Code:

  • New translations: i18n/en.pot
  • New legend type constants: src/constants/layers.js
  • New classification utilities: src/util/classify.js
  • Fix radio button value type cast for new classification IDs: src/components/classification/LegendTypeSelect.jsx
  • New dependency on simple-statistics: package.json and yarn.lock

imageimage

DHIS2-20818: Thematic layer, bubble style - legends shows NaN when the all mapped values are equal

JIRA ticket: DHIS2-20818

Test map: nxq2hIlZdAK

Details

+ Code:

  • Handle the minValue === maxValue edge case in bubble radius calculation: src/util/bubbles.js
image

DHIS2-20287: Hide or show map layers while on the dashboard like it is the charts

JIRA ticket: DHIS2-20287

Test map: vk5afupHOqr?interpretationId=PQfGfYQRzG8

Details

+ Changes:

  • Add a visibility toggle button (eye icon) in the plugin legend panel, one per layer

+ Code:

  • Visibility toggle button with show/hide icon per layer: src/components/plugin/LegendLayer.jsx
  • toggleLayerVisibility callback and visibilityOverrides state (reset when map views change): src/components/plugin/Map.jsx
  • Passes toggleLayerVisibility down to LegendLayer: src/components/plugin/Legend.jsx
  • Styles for the visibility button: src/components/plugin/styles/Legend.css
image

DHIS2-19984: Values not displayed for data item with type NUMBER and associated to an option set in Maps app data table

JIRA ticket: DHIS2-19984

Test map: wDv1BBwTCyQ

Details

+ Changes:

  • Treat NUMBER type data items associated with an option set as text in the data table

+ Code:

  • Detect NUMBER type with option set and render value as text: src/components/datatable/useTableData.js
  • Pass option type info from the analytics response: src/loaders/eventLoader.js
image

DHIS2-19983: Improve ranges/legend sorting in Maps data table

JIRA ticket: DHIS2-19983

Test map:

Details

+ Changes:

  • Sort the legend range column numerically by parsing start and end values from the range string, instead of lexicographic string comparison
  • Store the raw numeric value separately (rawValue) so the value column also sorts numerically

+ Code:

  • Numeric range parsing for RANGE column sort, renamed value key to rawValue: src/components/datatable/useTableData.js
  • New parseRange utility (handles digit-group separators): src/util/legend.js

DHIS2-19982: Chevron icon is not centered in the color select button

JIRA ticket: DHIS2-19982

Test map:

Details

+ Changes:

  • The color picker buttons used in "Style by data item" for option set values and boolean (Yes/No, Yes only) values were only 32px wide
  • At that width the chevron icon appeared like it should be visually centered and obscuring the selected color — unlike wider color pickers elsewhere in the app where the chevron at the right is unambiguous
  • Increased the width to 50px and let the chevron sit at the right, consistent with all other color pickers in the app

+ Code:

  • Pass width={50} to the ColorPicker in the option style row: src/components/optionSet/OptionStyle.jsx
  • Remove the fixed width: 32px; height: 32px constraint and switch to flexbox layout: src/components/optionSet/styles/OptionStyle.module.css
  • BooleanStyle is unaffected directly as it reuses OptionStyle: src/components/dataItem/BooleanStyle.jsx
  • Margin adjustment for chevron alignment in the color scale selector: src/components/core/styles/ColorScale.module.css

DHIS2-19850: Thematic / Facility / OrgUnits / Earth Engine layer - Deal with OrgUnits without coordinates

JIRA ticket: DHIS2-19850

Test map:

Details

+ Changes:

  • Added a "Count org units without coordinates" checkbox in Thematic, Earth Engine, Facility, and Org Unit layer configuration dialogs
  • When enabled, a "Data quality" section appears in the legend (outside the legend items table) reporting the count of org units without coordinates
  • Org units without coordinates are included in the data table when the option is enabled

+ Code:

  • New countOrgUnitsWithoutCoordinates action and reducer: src/actions/layerEdit.js
  • Checkbox added to layer dialogs: src/components/edit/FacilityDialog.jsx, src/components/edit/earthEngine/StyleTab.jsx, src/components/edit/orgUnit/OrgUnitDialog.jsx, src/components/edit/thematic/ThematicDialog.jsx
  • "Data quality" section in legend shared with events without coordinates, rendered outside the legend items table: src/components/legend/Legend.jsx
  • Fetch and count org units without coordinates in loaders: src/loaders/facilityLoader.js, src/loaders/orgUnitLoader.js, src/loaders/thematicLoader.js, src/loaders/earthEngineLoader.js
  • Org unit fetching utilities: src/util/orgUnits.js, src/util/requests.js
  • Include org units without coordinates in the data table: src/components/datatable/useTableData.js
  • Persist setting: src/util/favorites.js, src/util/config.js

DHIS2-19812: Properly distinguish values outside of legend and no data and allow user to display both independently

JIRA ticket: DHIS2-19812

Test map:

Details

+ Changes:

  • Split the former single "no data color" setting into two independent options, for both Thematic and Event layers:
    • "No data" / "No data events": features that have no value at all
    • "Unclassified" / "Unclassified events": features whose value falls outside all legend ranges
  • Each option has its own configurable color and display name
  • Both are rendered in the bubble legend

+ Code:

  • New NoDataLegend reusable component (checkbox + color picker + name field): src/components/edit/shared/NoDataLegend.jsx
  • New UnclassifiedLegend reusable component (checkbox + color picker + name field): src/components/edit/shared/UnclassifiedLegend.jsx
  • Deleted the former single-field NoDataColor component (replaced by the two above): src/components/edit/thematic/NoDataColor.jsx
  • Thematic dialog updated to use both new components: src/components/edit/thematic/ThematicDialog.jsx
  • Event dialog updated to use both new components (with event-specific labels): src/components/edit/event/EventDialog.jsx
  • Loaders updated to handle noDataLegend and unclassifiedLegend separately (including migration from legacy noDataColor): src/loaders/thematicLoader.js, src/loaders/eventLoader.js
  • Style-by-data-item updated for all three modes (boolean, numeric, option set) to apply noDataLegend and unclassifiedLegend independently: src/util/styleByDataItem.js
  • Map layer updated to apply both independently: src/components/map/layers/ThematicLayer.jsx
  • Bubble legend updated to render the no-data class entry: src/components/legend/Bubbles.jsx
  • Persist both settings: src/util/favorites.js
  • Tests updated: src/util/__tests__/styleByDataItem.spec.js

DHIS2-19203: Use "Classes" instead of "Steps"

JIRA ticket: DHIS2-19203

Test map:

Details

+ Changes:

  • Renamed the "Steps" label to "Classes" in the Earth Engine layer style configuration panel, for consistency with the rest of the app

+ Code:

  • Label rename in: src/components/edit/earthEngine/StyleSelect.jsx

DHIS2-19078: Save visibility toggle state of layers and visibility and opacity of basemap with map

JIRA ticket: DHIS2-19078

Test map:

Details

+ Changes:

  • Layer visibility (shown/hidden) is now persisted when saving a map and restored when opening it
  • Basemap opacity and selected basemap ID are now saved even when the basemap is hidden

+ Code:

  • Persist isVisible for all layers and basemap opacity/ID: src/util/favorites.js
  • Migration for maps without saved visibility state: src/util/getMigratedMapConfig.js
  • Read serverVersion to apply migration conditionally: src/components/app/FileMenu.jsx
  • Pass visibility through all layer components: src/components/map/layers/EventLayer.jsx, FacilityLayer.jsx, GeoJsonLayer.js, Layer.js, OrgUnitLayer.jsx, ThematicLayer.jsx, TrackedEntityLayer.jsx, ExternalLayer.js, earthEngine/EarthEngineLayer.jsx
  • All loaders updated to forward the visibility state: src/loaders/thematicLoader.js, eventLoader.js, facilityLoader.js, orgUnitLoader.js, geoJsonUrlLoader.js, earthEngineLoader.js, externalLoader.js, trackedEntityLoader.js
  • Plugin map respects visibility overrides: src/components/plugin/Map.jsx
  • Tests: src/util/__tests__/favorites.spec.js, src/util/__tests__/getMigratedMapConfig.spec.js

DHIS2-18963: Digit groups separator functionality in maps

JIRA ticket: DHIS2-18963

Test maps: gugP3BzGjK1 - thematic) / UPtjWBCx4pJ - events / lL0i0WO23Gw - tracked entities / DpskVx1d2Jh - earth engine / gmSDDyzZHy2 - geojson / WR6S8roHTao - org unit / N3N3m9zyKy5 - facility

Details

+ Changes:

Values/counts are formatted with the system digit group separator and appear in:

  • Thematic layer: Legend (range — Choropleth and Bubble — + count), Tooltip, Pop-up, Data table, Organisation unit profile
  • Event layer: Legend (value — when styling by data item — + count), Pop-up
  • Tracked entities layer: Pop-up
  • Earth Engine layer: Legend (value), Pop-up, Data table, Organisation unit profile
  • GeoJSON layer: Feature profile
  • Org unit layer: Organisation unit profile
  • Facility layer: Organisation unit profile

+ Code:

  • Core formatting utility formatWithSeparator and parsing utility parseWithSeparator: src/util/numbers.js
  • Format helper updated to use separator: src/util/helpers.js
  • Add keyAnalysisDigitGroupSeparator to fetched system settings: src/constants/settings.js
  • Read separator from system settings and pass it to all loaders: src/hooks/useLayersLoader.js, src/components/plugin/LayerLoader.jsx
  • Legend range/value and count formatting: src/components/legend/LegendItemRange.jsx, src/components/legend/Bubble.jsx
  • Earth Engine legend preview: src/components/edit/earthEngine/LegendPreview.jsx
  • Pop-up formatting: src/components/map/layers/EventPopup.jsx, TrackedEntityPopup.jsx, earthEngine/EarthEnginePopup.jsx
  • Organisation unit profile formatting: src/components/orgunits/OrgUnitData.jsx, src/components/orgunits/OrgUnitInfo.jsx
  • Data table value and range formatting: src/components/datatable/useTableData.js, src/components/datatable/DataTable.jsx
  • Loader-side formatting for tooltips, labels and data table: src/loaders/thematicLoader.js, src/loaders/earthEngineLoader.js, src/loaders/eventLoader.js, src/loaders/trackedEntityLoader.js
  • GeoJSON feature profile formatting: src/components/map/layers/GeoJsonLayer.js, src/loaders/geoJsonUrlLoader.js

DHIS2-15514: Isolate a single value (eg. 0) in Maps

JIRA ticket: DHIS2-15514

Test map:

Details

+ Changes:

  • Added an "Isolated class" option in the automatic classification panel
  • When enabled, a numeric range (min/max, default 0–0) is pulled out of the classification into its own dedicated legend class with a configurable color and name
  • The remaining values are then classified normally into the requested number of classes
  • The isolated class is also rendered in the bubble legend

+ Code:

  • New IsolatedClass component (checkbox + min/max fields + color picker + name field): src/components/classification/IsolatedClass.jsx
  • Rendered inside the classification panel: src/components/classification/Classification.jsx
  • Logic to remove isolated range from classified items and prepend it as a separate class: src/util/legend.js
  • Apply isolation before classifying values: src/loaders/thematicLoader.js
  • Bubble legend updated to render the isolated class entry: src/components/legend/Bubbles.jsx
  • Persist isolated class setting: src/util/favorites.js
  • Tests updated: src/util/__tests__/legend.spec.js

DHIS2-12860: Equal counts distribution is not ideal when many values are the same

JIRA ticket: DHIS2-12860

Test map: APqEkeE0Nr5

Details

+ Changes:

  • Remove duplicate classes that result from equal counts classification when many values are identical (this may result in fewer classes than specified, but creating unnecessary empty classes serves no purpose)
  • Improve class matching so that a class where startValue === endValue (e.g. 100–100) can actually be matched against data values
  • Natural breaks classification (introduced with DHIS2-21142) also addresses this issue more fundamentally

+ Code:

  • Remove duplicate classes: src/util/classify.js
  • Allow value matching when startValue === endValue: src/util/classify.js
image

DHIS2-10823: Improve the predefined legend display

JIRA ticket: DHIS2-10823

Test map: oXqCWc5JtS5

Details

+ Changes:

  • The existing de-duplication only hid the range when the legend item name was exactly "{startValue} - {endValue}"
  • Implemented a more robust check that also handles names like "100+", "50 or below", "100 000" or "100,000" — i.e. names that already contain the range information in any common format
  • When none of the names contain range information, the range is shown alongside the name as before

+ Code:

  • Add showRange flag in: src/components/legend/Legend.jsx
  • Pass showRange flag down in: src/components/legend/LegendItem.jsx
  • Use showRange flag to conditionally render the range in: src/components/legend/LegendItemRange.jsx
  • New legendNamesContainRange utility: src/util/legend.js
image

DHIS2-8478: Automatic legends with few data points

JIRA ticket: DHIS2-8478

Test map: r56Jrw3ZOQs

Details

+ Changes:

  • When the number of distinct values is less than the requested number of classes, create one class per distinct value instead of generating empty classes
  • This may result in fewer classes than specified, but there is no point in creating unnecessary empty classes

+ Code:

  • Add hasInsufficientValues flag and reduce class count accordingly: src/util/classify.js
image

DHIS2-3156: Allow setting of numeric precision in automatic map legends

JIRA ticket: DHIS2-3156

Test map:

Details

+ Changes:

  • Added a "Decimal places" selector (Auto / 0–4) in the automatic legend classification panel and in the single-color panel
  • The selected precision is applied consistently to all legend value formatting: legend range boundaries, tooltips, pop-ups, and the data table value column

+ Code:

  • New "Decimal places" SelectField in the classification panel: src/components/classification/Classification.jsx
  • New "Decimal places" SelectField in the single-color panel: src/components/classification/SingleColor.jsx
  • New legendDecimalPlaces action and reducer: src/actions/layerEdit.js
  • Apply decimal places when computing legend item boundaries: src/util/classify.js
  • Format numbers respecting decimal places and digit group separator: src/util/numbers.js, src/util/helpers.js
  • Propagate decimal places through loaders: src/loaders/thematicLoader.js, src/loaders/eventLoader.js
  • Data table value column respects decimal places: src/components/datatable/useTableData.js
  • Persist setting: src/util/favorites.js
  • Tests updated: src/util/__tests__/classify.spec.js

DHIS2-237: Deal with events without coordinates

JIRA ticket: DHIS2-237

Test map: oYiqu1VidCh

Details

+ Changes:

  • Added a "Count events without coordinates" checkbox in the Events layer configuration dialog
  • When enabled, a "Data quality" section appears in the legend (outside the legend items table) reporting the count of events without coordinates
  • Events without coordinates are included in the data table when the option is enabled

+ Code:

  • "Count events without coordinates" checkbox added inline to the Events layer dialog: src/components/edit/event/EventDialog.jsx
  • Load events and count those without coordinates: src/loaders/eventLoader.js
  • Count events without coordinates from analytics response: src/util/geojson.js
  • "Data quality" section rendered outside the legend items table: src/components/legend/Legend.jsx
  • Include events without coordinates in the data table: src/components/datatable/useTableData.js
  • Parse countEventsWithoutCoordinates from saved map config: src/util/event.js
  • Persist setting: src/util/favorites.js
  • Tests updated: src/components/datatable/__tests__/useTableData.spec.jsx
image

Others

Details
  • Fix maxValue identification error in: src/loaders/thematicLoader.js
  • More robust legend items sorting in: src/util/legend.js
  • Use consistent formatting method to create classes and classify values:
    • Get getLegendItems to also return valueFormat: src/util/classify.js
    • Get getAutomaticLegendItems to also return valueFormat: src/util/legend.js
    • Pass valueFormat through: src/loaders/thematicLoader.js
    • Use valueFormat in getLegendItemForValue: src/util/classify.js
    • Tests updated: src/util/__tests__/classify.spec.js, src/util/__tests__/legend.spec.js

Quality checklist

Add N/A to items that are not applicable.

@dhis2-bot
Copy link
Copy Markdown
Contributor

dhis2-bot commented Mar 20, 2026

🚀 Deployed on https://pr-3637.maps.netlify.dhis2.org

@dhis2-bot dhis2-bot temporarily deployed to netlify March 20, 2026 15:10 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify March 23, 2026 12:54 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify March 23, 2026 13:53 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify March 23, 2026 16:21 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify March 23, 2026 16:42 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify March 23, 2026 16:47 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 7, 2026 12:21 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 7, 2026 15:18 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 10, 2026 15:10 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 13, 2026 10:49 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 13, 2026 11:13 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 13, 2026 11:25 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 13, 2026 11:57 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 13, 2026 12:36 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 16, 2026 15:40 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 17, 2026 09:39 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 17, 2026 09:52 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 17, 2026 15:42 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 17, 2026 21:21 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 17, 2026 21:45 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 20, 2026 09:38 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 21, 2026 09:39 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 21, 2026 10:31 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 21, 2026 11:55 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 21, 2026 16:20 Inactive
@BRaimbault BRaimbault changed the title feat: new classifications and legend improvements feat: new classifications and legend improvements [DHIS2-18242] Apr 22, 2026
@dhis2-bot dhis2-bot temporarily deployed to netlify April 22, 2026 12:24 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify April 22, 2026 17:28 Inactive
@sonarqubecloud
Copy link
Copy Markdown

@dhis2-bot dhis2-bot temporarily deployed to netlify April 22, 2026 17:32 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants