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
10 changes: 8 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-10T13:44:55.087Z\n"
"PO-Revision-Date: 2026-09-10T13:44:55.087Z\n"

msgid "2020"
msgstr "2020"
Expand Down Expand Up @@ -38,6 +38,9 @@ msgstr "Classification"
msgid "Classes"
msgstr "Classes"

msgid "Color scale"
msgstr "Color scale"

msgid "Auto"
msgstr "Auto"

Expand Down Expand Up @@ -437,6 +440,9 @@ msgstr "Group events"
msgid "View all events"
msgstr "View all events"

msgid "View heat map"
msgstr "View heat map"

msgid "Radius"
msgstr "Radius"

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@dhis2/analytics": "^29.5.5",
"@dhis2/app-runtime": "^3.17.3",
"@dhis2/app-service-datastore": "^1.0.0-beta.3",
"@dhis2/maps-gl": "^4.4.3",
"@dhis2/maps-gl": "git+https://github.com/d2-ci/maps-gl.git#a0c0a650843414776d42d94d38b476499823cffb",
"@dhis2/ui": "^10.17.0",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^9.0.0",
Expand Down
Binary file added public/images/heatmap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/actions/layerEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ export const setFallbackCoordinateField = (fieldId) => ({
fieldId,
})

// Set if event heatmap should be used (event)
export const setEventHeatmap = (checked) => ({
type: types.LAYER_EDIT_EVENT_HEATMAP_SET,
checked,
})

// Set if event clustering should be used (event)
export const setEventClustering = (checked) => ({
type: types.LAYER_EDIT_EVENT_CLUSTERING_SET,
Expand Down
14 changes: 14 additions & 0 deletions src/actions/layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ export const changeLayerOpacity = (id, opacity) => ({
opacity,
})

// Set overlay heat intensity
export const changeLayerIntensity = (id, heatIntensity) => ({
type: types.LAYER_CHANGE_INTENSITY,
id,
heatIntensity,
})

// Set overlay heat radius
export const changeLayerRadius = (id, heatRadius) => ({
type: types.LAYER_CHANGE_RADIUS,
id,
heatRadius,
})

// Change ordering of overlays
export const sortLayers = ({ oldIndex, newIndex }) => ({
type: types.LAYER_SORT,
Expand Down
38 changes: 38 additions & 0 deletions src/components/classification/ContinuousScale.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import i18n from '@dhis2/d2-i18n'
import PropTypes from 'prop-types'
import React from 'react'
import { connect } from 'react-redux'
import { setColorScale } from '../../actions/layerEdit.js'
import { getColorPalette } from '../../util/colors.js'
import { ColorScaleSelect } from '../core/index.js'
import styles from './styles/Classification.module.css'

const ContinuousScale = ({ colorScale, setColorScale }) => {
if (!colorScale) {
setColorScale(getColorPalette('RdYlBu_reverse', 9))
}
return (
<div>
<div className={styles.title}>{i18n.t('Color scale')}</div>
<ColorScaleSelect
palette={colorScale ?? getColorPalette('RdYlBu_reverse', 9)}
onChange={setColorScale}
width={190}
heat={true}
className={styles.scale}
/>
</div>
)
}

ContinuousScale.propTypes = {
setColorScale: PropTypes.func.isRequired,
colorScale: PropTypes.array,
}

export default connect(
({ layerEdit }) => ({
colorScale: layerEdit.colorScale,
}),
{ setColorScale }
)(ContinuousScale)
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
.title {
color: var(--colors-grey900);
padding-top: 8px;
padding-left: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.select {
width: 100%;
}
Expand Down
26 changes: 15 additions & 11 deletions src/components/core/ColorScaleSelect.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import PropTypes from 'prop-types'
import React, { Fragment, useState, useRef } from 'react'
import {
colorScales,
heatScales,
getColorScale,
getColorPalette,
} from '../../util/colors.js'
import ColorScale from './ColorScale.jsx'
import styles from './styles/ColorScaleSelect.module.css'

const ColorScaleSelect = ({ palette, width, onChange, className }) => {
const ColorScaleSelect = ({ palette, width, heat, onChange, className }) => {
const [isOpen, setIsOpen] = useState(false)
const anchorRef = useRef()

const bins = palette.length
const scale = getColorScale(palette)
const scale = getColorScale(palette, heat)

const onColorScaleSelect = (scale) => {
onChange(getColorPalette(scale, bins))
Expand Down Expand Up @@ -43,15 +44,17 @@ const ColorScaleSelect = ({ palette, width, onChange, className }) => {
className={styles.popover}
style={{ width: width + 24 || 260 }}
>
{colorScales.map((scale, index) => (
<ColorScale
key={index}
scale={scale}
bins={bins}
onClick={onColorScaleSelect}
width={width || 260}
/>
))}
{(heat ? heatScales : colorScales).map(
(scale, index) => (
<ColorScale
key={index}
scale={scale}
bins={bins}
onClick={onColorScaleSelect}
width={width || 260}
/>
)
)}
</div>
</Popover>
)}
Expand All @@ -63,6 +66,7 @@ ColorScaleSelect.propTypes = {
palette: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
className: PropTypes.string,
heat: PropTypes.bool,
width: PropTypes.number,
}

Expand Down
33 changes: 27 additions & 6 deletions src/components/edit/event/EventDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
setEventStatus,
setEventCoordinateField,
setEventClustering,
setEventHeatmap,
setEventPointColor,
setEventPointRadius,
// setFallbackCoordinateField,
Expand Down Expand Up @@ -41,6 +42,7 @@
splitFilterColumns,
} from '../../../util/analytics.js'
import { cssColor } from '../../../util/colors.js'
import ContinuousScale from '../../classification/ContinuousScale.jsx'
import { isValidIsolatedClass } from '../../classification/IsolatedClass.jsx'
import {
Tab,
Expand Down Expand Up @@ -77,6 +79,7 @@
eventClustering,
eventCoordinateField,
eventCoordinateFieldType,
eventHeatmap,
eventPointColor,
eventPointRadius,
eventStatus,
Expand Down Expand Up @@ -448,20 +451,35 @@
id="cluster"
img="images/cluster.png"
title={i18n.t('Group events')}
onClick={() =>
onClick={() => {
dispatch(setEventClustering(true))
}
dispatch(setEventHeatmap(false))
}}
isSelected={eventClustering}
className={styles.flexInnerColumn}
/>
<ImageSelect
id="nocluster"
img="images/nocluster.png"
title={i18n.t('View all events')}
onClick={() =>
onClick={() => {
dispatch(setEventClustering(false))
dispatch(setEventHeatmap(false))
}}
isSelected={
!eventClustering && !eventHeatmap
}
isSelected={!eventClustering}
className={styles.flexInnerColumn}
/>
<ImageSelect
id="heat"
img="images/heatmap.png"
title={i18n.t('View heat map')}
onClick={() => {
dispatch(setEventClustering(false))
dispatch(setEventHeatmap(true))
}}
isSelected={eventHeatmap}
className={styles.flexInnerColumn}
/>
</div>
Expand Down Expand Up @@ -494,7 +512,7 @@
</div>
<GeometryCentroid tab={'style'} />
<BufferRadius
disabled={eventClustering}
disabled={eventClustering || eventHeatmap}
defaultRadius={EVENT_BUFFER}
/>
<LabelFieldSelect />
Expand Down Expand Up @@ -526,19 +544,21 @@
/>
</div>
<div className={styles.flexColumn}>
{program ? (
{eventHeatmap ? (
<ContinuousScale />
) : program ? (
<StyleByDataItem
error={!legendSet && legendSetError}
/>
) : (
<div className={styles.notice}>
<NoticeBox>
{i18n.t(
'You can style events by data element after selecting a program.'
)}
</NoticeBox>
</div>
)}

Check warning on line 561 in src/components/edit/event/EventDialog.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=dhis2_maps-app&issues=AaCLt6BKmfHPkMg5AeXS&open=AaCLt6BKmfHPkMg5AeXS&pullRequest=3770
</div>
</div>
)}
Expand All @@ -559,6 +579,7 @@
eventClustering: PropTypes.bool,
eventCoordinateField: PropTypes.string,
eventCoordinateFieldType: PropTypes.string,
eventHeatmap: PropTypes.bool,
eventPointColor: PropTypes.string,
eventPointRadius: PropTypes.number,
eventStatus: PropTypes.string,
Expand Down
45 changes: 45 additions & 0 deletions src/components/layers/overlays/HeatSlider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import PropTypes from 'prop-types'
import React, { useCallback } from 'react'
import styles from './styles/HeatSlider.module.css'

const HeatSlider = ({ heat, disabled, onChange }) => {
const lowerFill = `var(--colors-grey${disabled ? 400 : 600})`
const upperFill = `var(--colors-grey${disabled ? 300 : 400})`

const onSliderChange = useCallback(
(evt) => onChange(Number(evt.target.value)),
[onChange]
)

return (
<div className={styles.container}>
<input
type="range"
min="0"
max="1"
step="0.01"
value={heat}
disabled={disabled}
onChange={onSliderChange}
className={styles.slider}
style={{
background: `linear-gradient(
to right,
${lowerFill} 0%,
${lowerFill} ${heat * 100}%,
${upperFill} ${heat * 100}%,
${upperFill} 100%
)`,
}}
/>
</div>
)
}

HeatSlider.propTypes = {
heat: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool,
}

export default HeatSlider
Loading
Loading