feat: no data / unclassified / isolated classes + thematicLoader refactor [PR5] [DHIS2-18242]#3649
Open
BRaimbault wants to merge 11 commits into
Open
feat: no data / unclassified / isolated classes + thematicLoader refactor [PR5] [DHIS2-18242]#3649BRaimbault wants to merge 11 commits into
BRaimbault wants to merge 11 commits into
Conversation
…mes [DHIS2-10823]
Contributor
|
🚀 Deployed on https://pr-3649.maps.netlify.dhis2.org |
BRaimbault
commented
Apr 30, 2026
Collaborator
Author
BRaimbault
left a comment
There was a problem hiding this comment.
Ready for review
2e014f4 to
eefbdfd
Compare
edoardo
approved these changes
May 12, 2026
Member
edoardo
left a comment
There was a problem hiding this comment.
It's a big PR, but I tried poking around in the examples and it looks good.
I guess a proper KFMT would be good.
7eaf47d to
bd277ac
Compare
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Parent
Implements
decimalPlacesfrom legend items to the legend object (follow-up from PR4)Overview
Fifth PR in the series extracted from the parent epic. Adds configurable no data and unclassified classes that let users assign a color and label to org units with missing values or values outside the legend range. Adds a configurable isolated class that pins a specific value range to a fixed color, independent of the automatic classification. Improves predefined legend display by detecting when item names already encode the range. Fixes logarithmic and standard deviation classification to route non-classifiable values to the unclassified bucket rather than silently degrading them. Includes a significant
thematicLoaderrefactor to support all of the above cleanly.Changes
No data and unclassified classes DHIS2-19812
Two new optional legend classes are added to the thematic layer dialog:
noDataColor); now both color and name are configurable via anoDataLegendobject.Both are rendered as optional rows in the dialog using a new shared
OptionalLegendItemcomponent. The values are stored asnoDataLegendandunclassifiedLegendon the layer config, serialized intoconfigJSON on save (withnoDataColoralso written for backward compatibility), and parsed back on load viaparseJsonConfig.In
thematicLoader, the two items are appended tolegend.itemswithisNoData: true/isUnclassified: trueflags. Feature styling helpers (getFeatureColor,getFeatureLegend,getFeatureRadius,countLegendItem) use these flags to assign color, legend text, radius, and count. Features with no matching class are either routed to the configured class or filtered out of the data array.The same routing is applied in
styleByDataItem.jsfor event layer style-by-data-item:isUnclassifiedis now set for any valued feature that returns no legend item, not only for predefined classification.For the logarithmic method, non-positive values are filtered from the classification input (instead of falling back to equal intervals), and clamping is disabled so they return
undefined→isUnclassified. For the standard deviation method, breaks are no longer clipped to[minValue, maxValue]; instead σ-aligned outer bounds are computed atμ ± (maxOffset + 1)σ, and values outside those bounds returnundefined→isUnclassified.The unclassified class is also supported for facilities and org units layers when styled by a group set. In
getStyledOrgUnits(orgUnits.js), features with no matching group in the active group set receive theunclassifiedLegendcolor and are retained in the feature array — previously, facilities with no group membership were always filtered out.Files:
src/loaders/thematicLoader.js,src/loaders/facilityLoader.js,src/loaders/orgUnitLoader.js,src/util/classify.js,src/util/legend.js,src/util/orgUnits.js,src/util/styleByDataItem.js,src/util/favorites.js,src/components/edit/shared/NoDataLegend.jsx,src/components/edit/shared/UnclassifiedLegend.jsx,src/components/edit/shared/OptionalLegendItem.jsx,src/components/edit/thematic/ThematicDialog.jsx,src/components/edit/event/EventDialog.jsx,src/components/edit/FacilityDialog.jsx,src/components/edit/orgUnit/OrgUnitDialog.jsx,src/components/legend/Bubbles.jsx,src/components/legend/Legend.jsx,src/components/map/layers/ThematicLayer.jsx,src/constants/actionTypes.js,src/actions/layerEdit.js,src/reducers/layerEdit.js,src/util/__tests__/orgUnits.spec.jsIsolated class DHIS2-15514
A configurable isolated class pins a specific value range
[min, max]to a fixed color. Values inside the range are assigned the isolated item and excluded from the automatic classification; values outside are classified normally. The isolated item is prepended tolegend.itemswithisIsolated: trueand is checked first ingetLegendItemForValue(before the regular range items). Clamping and range-find operations skip isolated, no-data, and unclassified items via the newisRegularLegendItempredicate.For bubble maps, non-isolated values drive
minValue/maxValueso the radius scale is not distorted by isolated values.Files:
src/util/classify.js,src/util/legend.js,src/loaders/thematicLoader.js,src/util/favorites.js,src/components/classification/IsolatedClass.jsx,src/components/classification/Classification.jsx,src/components/legend/Bubbles.jsx,src/constants/actionTypes.js,src/actions/layerEdit.js,src/reducers/layerEdit.jsPredefined legend range detection DHIS2-10823
legendNamesContainRange(items)returnstruewhen ≥ 50 % of items have theirstartValueorendValueembedded in the item name.Legend.jsxuses this to decide whether to hide the range column when it would duplicate the name. Previously, item names equal to"startValue - endValue"were silently cleared ingetPredefinedLegendItems; that clearing is removed — the detection is now done at render time so names are preserved in the data model.Files:
src/util/legend.js,src/components/legend/Legend.jsx,src/components/legend/LegendItem.jsx,src/components/legend/LegendItemRange.jsxMove
decimalPlacesfrom legend items to legend object DHIS2-3156Follow-up to PR4:
decimalPlacesis no longer stamped onto every individual legend item bygetAutomaticLegendItems. It is stored once onlegend.decimalPlacesand read from there wherever formatting is needed. This avoids the redundancy and keeps item objects clean.Files:
src/util/legend.js,src/loaders/thematicLoader.jsTests update
src/util/__tests__/classify.spec.js—getLegendItemForValuewith isolated / noData / unclassified items; log and SD new behaviorsrc/util/__tests__/legend.spec.js—isRegularLegendItem,buildIsolatedLegendItem,sortLegendItemswith isolated items,getAutomaticLegendItemswithlegendIsolated,legendNamesContainRangesrc/util/__tests__/favorites.spec.js—noDataLegend,unclassifiedLegend,legendIsolatedserializationManual testing
Netlify: https://pr-3649.maps.netlify.dhis2.org/ + Instance: https://dev.im.dhis2.org/maps-app-42-3
DHIS2-19812: No data and unclassified classes
Test maps
DHIS2-15514: Isolated class
Test maps
DHIS2-10823: Predefined legend range detection
Test maps
DHIS2-19812 & DHIS2-15514
Test maps
Thematic (Single / Timeline / Split):
Events:
Quality checklist