Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow choropleth to be toggled on and off #168

Merged
merged 8 commits into from
Dec 18, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,15 @@ const PoliticalChoropleths: React.FC<PoliticalChoroplethsProps> = ({
? 'visible'
: 'none'

const showChoropleth =
report.displayOptions?.dataVisualisation?.showDataVisualisation
?.choropleth ?? false

const { data: dataByBoundary } = useDataByBoundary({ report, boundaryType })
const map = useLoadedMap()
const [selectedBoundary, setSelectedBoundary] = useAtom(selectedBoundaryAtom)
useClickOnBoundaryEvents(visibility === 'visible' ? tileset : null)

useEffect(() => {
if (visibility === 'none') {
setSelectedBoundary(null)
}
}, [visibility])

// When the map is loaded and we have the data, add the data to the boundaries
useEffect(() => {
if (map.loaded && dataByBoundary) {
Expand Down Expand Up @@ -79,16 +77,22 @@ const PoliticalChoropleths: React.FC<PoliticalChoroplethsProps> = ({
promoteId={tileset.promoteId}
>
{/* Fill of the boundary */}
<Layer
beforeId="road-simple"
id={`${tileset.mapboxSourceId}-fill`}
source={tileset.mapboxSourceId}
source-layer={tileset.sourceLayerId}
type="fill"
filter={getChoroplethFillFilter(dataByBoundary, tileset)}
paint={getChoroplethFill(dataByBoundary, visibility === 'visible')}
//layout={{ visibility: delayedVisibility }}
/>
{showChoropleth && (
<>
<Layer
beforeId="road-simple"
id={`${tileset.mapboxSourceId}-fill`}
source={tileset.mapboxSourceId}
source-layer={tileset.sourceLayerId}
type="fill"
filter={getChoroplethFillFilter(dataByBoundary, tileset)}
paint={getChoroplethFill(
dataByBoundary,
visibility === 'visible'
)}
/>
</>
)}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will change the layer order in ways that might break the visualisation.

There is already a const visibility statement further up. Let's just add an extra condition into that instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done now I think

{/* Border of the boundary */}
<Layer
beforeId={PLACEHOLDER_LAYER_ID_CHOROPLETH}
Expand All @@ -111,7 +115,11 @@ const PoliticalChoropleths: React.FC<PoliticalChoroplethsProps> = ({
type="line"
paint={getSelectedChoroplethEdge()}
filter={['==', ['get', tileset.promoteId], selectedBoundary]}
layout={{ visibility, 'line-join': 'round', 'line-round-limit': 0.1 }}
layout={{
visibility,
'line-join': 'round',
'line-round-limit': 0.1,
}}
/>
</Source>
<Source
Expand All @@ -131,10 +139,8 @@ const PoliticalChoropleths: React.FC<PoliticalChoroplethsProps> = ({
'interpolate',
['exponential', 1],
['zoom'],
//
7.5,
0,
//
7.8,
1,
],
Expand All @@ -156,10 +162,8 @@ const PoliticalChoropleths: React.FC<PoliticalChoroplethsProps> = ({
'interpolate',
['exponential', 1],
['zoom'],
//
7.5,
0,
//
7.8,
1,
],
Expand Down
266 changes: 148 additions & 118 deletions nextjs/src/app/reports/[id]/(components)/ReportVisualisation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
import { startCase } from 'lodash'
import React from 'react'
import { VisualisationType } from '../reportContext'
import React, { useState } from 'react'
import { VisualisationLabels, VisualisationType } from '../reportContext'
import useDataByBoundary from '../useDataByBoundary'
import CollapsibleSection from './CollapsibleSection'
import { UpdateConfigProps } from './ReportConfiguration'
import { useReport } from './ReportProvider'

const ReportVisualisation: React.FC<UpdateConfigProps> = ({
updateVisualisationConfig,
}) => {
const { report } = useReport()
const { report, updateReport } = useReport()
const {
layers,
politicalBoundaries,
Expand All @@ -31,136 +31,166 @@ const ReportVisualisation: React.FC<UpdateConfigProps> = ({
boundaryType: dataVisualisation?.boundaryType,
})

const visualisationType = dataVisualisation?.visualisationType
const [checkedTypes, setCheckedTypes] = useState<Record<string, boolean>>(
() =>
Object.values(VisualisationType).reduce(
(acc, type) => ({
...acc,
[type]: type === dataVisualisation?.visualisationType,
}),
{}
)
)

const handleSwitchChange = (type: VisualisationType, checked: boolean) => {
setCheckedTypes((prev) => ({
...prev,
[type]: checked,
}))

updateReport({
displayOptions: {
...report.displayOptions,
dataVisualisation: {
...report.displayOptions.dataVisualisation,
showDataVisualisation: {
...Object.values(VisualisationType).reduce(
(acc, visType) => {
acc[visType] =
report.displayOptions.dataVisualisation
?.showDataVisualisation?.[visType] ?? false
return acc
},
{} as Record<VisualisationType, boolean>
),
[type]: checked,
},
visualisationType: checked ? type : undefined,
},
},
})
}
const dataSourceId = dataVisualisation?.dataSource
const dataSourceField = dataVisualisation?.dataSourceField
const selectedDataSource = layers.find((layer) => layer.id === dataSourceId)
const selectedBoundaryLabel = politicalBoundaries.find(
(boundary) => boundary.boundaryType === dataVisualisation?.boundaryType
)?.label

const isLoading = !fieldNames || fieldNames.length === 0

return (
<CollapsibleSection id="report-visualisation" title="Data Visualisation">
<div className="flex flex-col gap-3">
<div>
<Select
onValueChange={(type) =>
updateVisualisationConfig({
visualisationType: type as VisualisationType,
})
}
value={visualisationType}
>
<Label
htmlFor="select-vis-type"
className="text-white text-sm font-medium"
>
Type
</Label>
<SelectTrigger
id="select-vis-type"
disabled
className="w-full border-meepGray-100 text-meepGray-100 mt-2 font-medium"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.values(VisualisationType).map((type) => (
<SelectItem className="font-medium" key={type} value={type}>
{startCase(type)}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-meepGray-400 text-sm font-normal mb-3 mt-3">
Colour shading by category
</p>
</div>

{report.layers.length && (
<div>
<Select
onValueChange={(type) =>
updateVisualisationConfig({
dataSource: type as MapLayer['id'],
})
}
value={dataSourceId}
>
<div className="text-white text-sm font-medium">Type</div>
<div className="flex flex-col gap-2">
{Object.values(VisualisationType).map((type) => (
<div key={type} className="flex items-center space-x-2 mt-2">
<Switch
id={`switch-${type}`}
checked={checkedTypes[type]}
onCheckedChange={(checked) => handleSwitchChange(type, checked)}
/>
<Label
htmlFor="select-vis-type"
className="text-white text-sm font-medium"
htmlFor={`switch-${type}`}
>
Colour by
{startCase(type)}
</Label>
<SelectTrigger
id="select-vis-type"
className="w-full border-meepGray-100 text-meepGray-100 mt-2 font-medium"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{layers.map((layer) => (
<SelectItem
className="font-medium"
key={layer.id}
value={layer.id}
>
{startCase(layer.name)}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-meepGray-400 text-sm font-normal mb-3 mt-3">
Select which data will populate your {selectedBoundaryLabel}
</p>
</div>
)}
{selectedDataSource?.source.dataType === 'AREA_STATS' && (
<div>
<Select
onValueChange={(type) =>
updateVisualisationConfig({
dataSourceField: type as MapLayer['id'],
})
}
value={dataSourceField}
defaultOpen={!dataSourceField}
required
disabled={isLoading}
>
<Label
htmlFor="select-vis-type"
className="text-white text-sm font-medium"
>
Select data field
</Label>
<SelectTrigger
id="select-vis-type"
className="w-full border-meepGray-100 text-meepGray-100 mt-2 font-medium flex items-center"
>
{isLoading ? <LoadingIcon size={'18'} /> : <SelectValue />}
</SelectTrigger>
{!isLoading && (
<SelectContent>
{fieldNames.map((field) => (
<SelectItem
className="font-medium"
key={field}
value={field}
>
{field}
</SelectItem>
))}
</SelectContent>
{VisualisationLabels[type] && (
<span className="text-meepGray-400 text-xs ml-2">
{VisualisationLabels[type]}
</span>
)}
</Select>
<p className="text-meepGray-400 text-sm font-normal mb-3 mt-3">
Select the field from your data source
</p>
</div>
</div>
))}
</div>
{checkedTypes['choropleth'] && (
<>
{report.layers.length && (
<div>
<Select
onValueChange={(type) =>
updateVisualisationConfig({
dataSource: type as MapLayer['id'],
})
}
value={dataSourceId}
>
<Label
htmlFor="select-vis-type"
className="text-white text-sm font-medium"
>
Colour by
</Label>
<SelectTrigger
id="select-vis-type"
className="w-full border-meepGray-100 text-meepGray-100 mt-2 font-medium"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{layers.map((layer) => (
<SelectItem
className="font-medium"
key={layer.id}
value={layer.id}
>
{startCase(layer.name)}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-meepGray-400 text-sm font-normal mb-3 mt-3">
Select which data will populate your {selectedBoundaryLabel}
</p>
</div>
)}

{selectedDataSource?.source.dataType === 'AREA_STATS' && (
<div>
<Select
onValueChange={(type) =>
updateVisualisationConfig({
dataSourceField: type as MapLayer['id'],
})
}
value={dataSourceField}
defaultOpen={!dataSourceField}
required
disabled={isLoading}
>
<Label
htmlFor="select-vis-type"
className="text-white text-sm font-medium"
>
Select data field
</Label>
<SelectTrigger
id="select-vis-type"
className="w-full border-meepGray-100 text-meepGray-100 mt-2 font-medium flex items-center"
>
{isLoading ? <LoadingIcon size={'18'} /> : <SelectValue />}
</SelectTrigger>
{!isLoading && (
<SelectContent>
{fieldNames.map((field) => (
<SelectItem
className="font-medium"
key={field}
value={field}
>
{field}
</SelectItem>
))}
</SelectContent>
)}
</Select>
<p className="text-meepGray-400 text-sm font-normal mb-3 mt-3">
Select the field from your data source
</p>
</div>
)}
</>
)}
</div>
</CollapsibleSection>
Expand Down
Loading
Loading