Skip to content

Commit

Permalink
Merge pull request #738 from MTES-MCT/feature/insee-pop-charts
Browse files Browse the repository at this point in the history
WIP Création des graphiques de population
  • Loading branch information
alexisig authored Dec 3, 2024
2 parents cc84cf9 + 0e1b61d commit 4182133
Show file tree
Hide file tree
Showing 99 changed files with 1,768 additions and 1,044 deletions.
6 changes: 4 additions & 2 deletions assets/scripts/components/pages/Consommation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ const Consommation: React.FC<{ endpoint: string }> = ({ endpoint }) => {
'comparison_chart',
'chart_determinant',
'pie_determinant',
'surface_chart',
'surface_proportional_chart'
'surface_proportional_chart',
'population_density_chart',
'population_conso_progression_chart',
'population_conso_comparison_chart',
], isLoading);

useEffect(() => {
Expand Down
52 changes: 52 additions & 0 deletions assets/scripts/highcharts/lineargauge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Highcharts from 'highcharts';

(function lineargaugeModule(H)
{
H.seriesType('lineargauge', 'column', null, {
setVisible(...args)
{
H.seriesTypes.column.prototype.setVisible.apply(this, args)
if (this.markLine)
{
this.markLine[this.visible ? 'show' : 'hide']()
}
},
drawPoints()
{
const series = this
const { chart } = this
const { inverted } = chart
const { xAxis } = this
const { yAxis } = this
const point = this.points[0]

// Hide the column as it is unused for display purposes
if (point.graphic)
{
point.graphic.hide()
}

// Create or animate the marker
if (!this.markLine)
{
const path = inverted
? ['M', 0, 0, 'L', -5, -5, 'L', 5, -5, 'L', 0, 0, 'L', 0, xAxis.len]
: ['M', 0, 0, 'L', -5, -5, 'L', -5, 5, 'L', 0, 0, 'L', xAxis.len, 0]

this.markLine = chart.renderer.path(path)
.attr({
fill: series.color,
stroke: series.color,
'stroke-width': 1,
})
.add()
}

// Update position
this.markLine.animate({
translateX: inverted ? xAxis.left + yAxis.translate(point.y) : xAxis.left,
translateY: inverted ? xAxis.top : yAxis.top + yAxis.len - yAxis.translate(point.y),
})
},
})
}(Highcharts))
3 changes: 3 additions & 0 deletions assets/scripts/hooks/useHighcharts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import highchartsAccessibility from 'highcharts/modules/accessibility';
import DependencyWheel from 'highcharts/modules/dependency-wheel';
import Sankey from 'highcharts/modules/sankey';

// Importation du type personnalisé lineargauge
import '../highcharts/lineargauge.js';

highchartsExporting(Highcharts);
exportDataModule(Highcharts);
highchartsAccessibility(Highcharts);
Expand Down
64 changes: 37 additions & 27 deletions assets/scripts/map_libre/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,39 +137,49 @@ export default class Events
}
}

showArtifCommunesInfoBox(_event, _options)
showArtifCommunesInfoBox(_event)
{
if (!this.infoBoxNode) this.setInfoBox()

if (_event.features.length > 0)
if (!_event.features.length > 0)
{
const { properties } = _event.features[0]
const artifEvo = JSON.parse(properties.artif_evo)[0]
this.infoBoxNode.innerHTML = ''
return
}
const feature = _event.features[0]

this.infoBoxNode.innerHTML = `<div class="info-box__title"><strong>${_options.title}</strong></div>
<div class="fr-mr-2w"><strong>Commune:</strong> ${properties.name}</div>
<div class="fr-mr-2w"><strong>Code INSEE:</strong> ${properties.insee}</div>
<div class="fr-mr-2w"><strong>Surface:</strong> ${formatData('number', ['fr-FR', 'unit', 'hectare', 2], properties.area)}</div>
<div class="fr-mr-2w"><strong>Surface artificialisée:</strong> ${formatData('number', ['fr-FR', 'unit', 'hectare', 2], properties.surface_artif)}</div>
<div class="fr-mr-2w"><strong>Évolution de l'artificialisation entre ${artifEvo.year_old} et ${artifEvo.year_new}:</strong></div>
<table class="table table-striped table-sm table-borderless table-custom">
<thead>
<tr>
<th scope="col" class="fr-text--xs">Surface artificialisée</th>
<th scope="col" class="fr-text--xs">Surface désartificialisée</th>
<th scope="col" class="fr-text--xs">Artificialisation nette</th>
</tr>
</thead>
<tbody>
<tr>
<td class="fr-text--xs text-danger">${formatData('number', ['fr-FR', 'unit', 'hectare', 2], artifEvo.new_artif)}</td>
<td class="fr-text--xs text-success">${formatData('number', ['fr-FR', 'unit', 'hectare', 2], artifEvo.new_natural)}</td>
<td class="fr-text--xs ${artifEvo.new_artif > artifEvo.new_natural ? ' text-danger' : ' text-success'}">${formatData('number', ['fr-FR', 'unit', 'hectare', 2], artifEvo.net_artif)}</td>
</tr>
</tbody>
</table>`
this.infoBoxNode.classList.add('visible')
const { properties } = feature
const artifEvo = JSON.parse(properties.artif_evo)[0]

if (!artifEvo)
{
this.infoBoxNode.innerHTML = ''
return
}

this.infoBoxNode.innerHTML = `
<div class="fr-mr-2w"><strong>Commune :</strong> ${properties.name} (${feature.id})</div>
<div class="fr-mr-2w"><strong>Taux d'artificialisation :</strong> ${formatData('number', ['fr-FR', 'unit', 'percent', 2], properties.percent_artif)}</div>
<div class="fr-mr-2w"><strong>Surface :</strong> ${formatData('number', ['fr-FR', 'unit', 'hectare', 2], properties.area)}</div>
<div class="fr-mr-2w"><strong>Surface artificialisée :</strong> ${formatData('number', ['fr-FR', 'unit', 'hectare', 2], properties.surface_artif)}</div>
<div class="fr-mr-2w"><strong>Évolution de l'artificialisation entre ${artifEvo.year_old} et ${artifEvo.year_new} :</strong></div>
<table class="table table-striped table-sm table-borderless table-custom">
<thead>
<tr>
<th scope="col" class="fr-text--xs">Surface artificialisée</th>
<th scope="col" class="fr-text--xs">Surface désartificialisée</th>
<th scope="col" class="fr-text--xs">Artificialisation nette</th>
</tr>
</thead>
<tbody>
<tr>
<td class="fr-text--xs text-danger">${formatData('number', ['fr-FR', 'unit', 'hectare', 2], artifEvo.new_artif)}</td>
<td class="fr-text--xs text-success">${formatData('number', ['fr-FR', 'unit', 'hectare', 2], artifEvo.new_natural)}</td>
<td class="fr-text--xs ${artifEvo.new_artif > artifEvo.new_natural ? ' text-danger' : ' text-success'}">${formatData('number', ['fr-FR', 'unit', 'hectare', 2], artifEvo.net_artif)}</td>
</tr>
</tbody>
</table>`
this.infoBoxNode.classList.add('visible')
}

hideInfoBox()
Expand Down
36 changes: 36 additions & 0 deletions assets/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -727,3 +727,39 @@ Utilities
width: auto;
}
}

/*
Guide
*/
.guide-container {
display: flex;
align-items: center;
gap: 1.5rem;
padding: 1rem;
border-radius: 6px;
background: #C8F2E5;
margin-bottom: 2rem;
}

.guide-container--column {
flex-direction: column;
justify-content: space-around;
}

.guide-icon {
max-width: 75px;
height: auto;
}

.guide-title {
font-weight: 600;
font-size: 0.9em;
margin-bottom: 0.9rem;
}

.guide-description {
font-size: 0.8em;
padding: 0;
margin: 0;
margin-bottom: 1rem;
}
51 changes: 1 addition & 50 deletions project/api_views.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
"""Public data API views."""

from django.contrib.gis.geos import Polygon
from django.db.models import F, OuterRef, Subquery
from django.http import JsonResponse
from rest_framework import generics, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ParseError

from public_data.models import Cerema, Commune
from public_data.models.gpu import ZoneUrba
from public_data.serializers import ZoneUrbaSerializer

from .models import Emprise, Project
from .serializers import (
EmpriseSerializer,
ProjectCommuneSerializer,
ProjectDetailSerializer,
)
from .views.mixins import UserQuerysetOrPublicMixin
from .serializers import EmpriseSerializer, ProjectDetailSerializer


class EmpriseViewSet(viewsets.ReadOnlyModelViewSet):
Expand All @@ -42,37 +27,3 @@ def get_queryset(self):
class ProjectDetailView(generics.RetrieveAPIView):
queryset = Project.objects.all()
serializer_class = ProjectDetailSerializer


class ProjectViewSet(UserQuerysetOrPublicMixin, viewsets.ReadOnlyModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectCommuneSerializer

@action(detail=True, methods=["get"])
def communes(self, request, pk):
project = self.get_object()
sum_function = sum(
[F(f) / 10000 for f in Cerema.get_art_field(project.analyse_start_date, project.analyse_end_date)]
)
sub_cerema = Cerema.objects.annotate(artif_area=sum_function)
sub_cerema = sub_cerema.filter(city_insee=OuterRef("insee"))
queryset = Commune.objects.annotate(
artif_area=Subquery(sub_cerema.values("artif_area")[:1]),
conso_1121_art=Subquery(sub_cerema.values("naf11art21")[:1]) / 10000,
conso_1121_hab=Subquery(sub_cerema.values("art11hab21")[:1]) / 10000,
conso_1121_act=Subquery(sub_cerema.values("art11act21")[:1]) / 10000,
).prefetch_related("communediff_set")

bbox = self.request.GET.get("in_bbox", None)
if bbox is not None and len(bbox) > 0:
polygon_box = Polygon.from_bbox(bbox.split(","))
queryset = queryset.filter(mpoly__bboverlaps=polygon_box)
serializer = ProjectCommuneSerializer(queryset, many=True)
return JsonResponse(serializer.data, status=200)

@action(detail=True, methods=["get"])
def zones(self, request, pk):
project = self.get_object()
queryset = ZoneUrba.objects.intersect(project.combined_emprise)
serializer = ZoneUrbaSerializer(queryset, many=True)
return JsonResponse(serializer.data, status=200)
19 changes: 7 additions & 12 deletions project/charts/AnnualConsoChart.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
HIGHLIGHT_COLOR,
LEGEND_NAVIGATION_EXPORT,
)
from public_data.domain.containers import PublicDataContainer
from public_data.models import AdminRef


Expand Down Expand Up @@ -35,18 +36,12 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def get_series(self):
if not self.series:
if self.level == "REGION":
self.series = self.project.get_land_conso_per_year("region_name")
elif self.level == "DEPART":
self.series = self.project.get_land_conso_per_year("dept_name")
elif self.level == "SCOT":
self.series = self.project.get_land_conso_per_year("scot")
elif self.level == "EPCI":
self.series = self.project.get_land_conso_per_year("epci_name")
else:
self.series = self.project.get_city_conso_per_year(group_name=self.group_name)
return self.series
conso = PublicDataContainer.consommation_progression_service().get_by_land(
land=self.project.land_proxy,
start_date=self.project.analyse_start_date,
end_date=self.project.analyse_end_date,
)
return {f"{self.project.territory_name}": {f"{c.year}": c.total for c in conso.consommation}}

def add_series(self):
super().add_series()
Expand Down
6 changes: 6 additions & 0 deletions project/charts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
"NetArtifComparaisonChartExport",
"ImperNetteProgression",
"ImperNetteProgressionExport",
"PopulationDensityChart",
"PopulationConsoProgressionChart",
"PopulationConsoComparisonChart",
]

from .AnnualArtifChart import AnnualArtifChart
Expand Down Expand Up @@ -89,6 +92,9 @@
CouvertureProgressionChart,
CouvertureProgressionChartExport,
)
from .demography.PopulationConsoComparisonChart import PopulationConsoComparisonChart
from .demography.PopulationConsoProgressionChart import PopulationConsoProgressionChart
from .demography.PopulationDensityChart import PopulationDensityChart
from .impermeabilisation.ImperByCouverturePieChart import (
ImperByCouverturePieChart,
ImperByCouverturePieChartExport,
Expand Down
6 changes: 0 additions & 6 deletions project/charts/consommation/AnnualConsoComparisonChart.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@ class AnnualConsoComparisonChart(ProjectChart):
def param(self):
return super().param | {
"title": {"text": "Consommation d'espace du territoire et des territoires similaires (en ha)"},
"subtitle": {
"text": (
"Proposition de territoires de même maille administrative, "
"il est possible de modifier cette selection dans la légende"
),
},
"yAxis": {"title": {"text": "Consommé (en ha)"}},
"xAxis": {"type": "category"},
"tooltip": {
Expand Down
2 changes: 2 additions & 0 deletions project/charts/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,5 @@ def missing_ocsge_diff_message(missing_indicateur: str) -> str:
DESARTIFICIALISATION_COLOR = "#00e272"
ARTIFICIALISATION_NETTE_COLOR = "#6a6af4"
HIGHLIGHT_COLOR = "#fa4b42"

DENSITY_MAX_IN_HA = 250 # Valeur arbitraire maximale de densité de population, Paris étant la plus elevée à 205 hab/ha
Loading

0 comments on commit 4182133

Please sign in to comment.