Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
5ebe8a3
Deleted the tests that werent compatible with carpool, down from 58% …
Sep 26, 2025
562c3d9
Added only previous working tests and asset.py test up to 100% covera…
Sep 29, 2025
33e64bd
Added set_params.py tests up to 100% and 26% global coverage
Sep 29, 2025
4918a1b
Added tests for population.py up to 100% coverage, 27% global coverag…
Sep 29, 2025
afe0a0a
Corrected tests for asset.py that crashed because of similar fixture …
Sep 29, 2025
779b7a8
Tests for trips.py at 100% coverage, global coverage up to 29%
Sep 29, 2025
f5e69bb
Tests for transport_zones.py added up to 100% coverage and 30% total …
Sep 29, 2025
ce017f2
Merge pull request #2 from adam-benyekkou/carpool-tests
adam-benyekkou Oct 3, 2025
d4649c3
WIP - Creation de l'interface de base mobility web - Header - Footer …
Oct 7, 2025
56a9056
Nettoyage / déplacement des comments
Oct 7, 2025
923170a
Modification du script d'installation pour la prise en charge de Linu…
Oct 14, 2025
fca5e7c
Changement des r_script et install_r_packages pour rester confirme à …
Oct 15, 2025
666ebb1
Corrected conflict on test_004 for PR
Oct 15, 2025
bcb5178
Corrected conflict on test_004 for PR missing space
Oct 15, 2025
220a41d
Merge pull request #3 from adam-benyekkou/dash-interface-mvp
adam-benyekkou Oct 15, 2025
eb30a80
Reajout du composant study_aread_summary.py qui avait disparu à cause…
Oct 15, 2025
aa91a79
feat - Ajout du composant ScenarioControl contenant pour l'instant un…
Oct 15, 2025
45d4e43
Ajout de deux panneaux latéraux, un avec les stats globales et une lé…
Oct 16, 2025
e2d9c15
Refactorisation des composants Map, StudyAreaSummary et Scenario_Cont…
Oct 17, 2025
110d518
fix - Le service map n'affichait pas le nouveau scénario en cas de cl…
Oct 17, 2025
a8f4cd7
Test d'intégration pour main et deux tests unitaires pour les geo uti…
Oct 17, 2025
27b1330
Ajout de tests pour main, 100% de coverage atteint
Oct 20, 2025
127552e
Changed host in app.run to reflect the real used host
Oct 20, 2025
190f7d6
Harmonizing files between branches
Oct 22, 2025
4258a57
Restore tests/ folder from dash-interface-mvp
Oct 22, 2025
41374ff
Restore tests/ folder from dash-interface-mvp
Oct 22, 2025
360a9b9
Merge pull request #4 from adam-benyekkou/dash-interface-query-form
adam-benyekkou Oct 22, 2025
8aa3483
Modified conftest to reflect main conftest
Oct 23, 2025
c5719c9
Merge pull request #6 from adam-benyekkou/dash-interface-mvp
adam-benyekkou Oct 23, 2025
17943d2
Edited pyproject.toml to match main
Oct 23, 2025
60b9733
Harmonized tests to match main
Oct 23, 2025
7cbcd5b
Solving conflicts on some mobility files
Oct 23, 2025
434e309
Merge branch '192--merge' into dash-interface-query-form
adam-benyekkou Oct 24, 2025
c3d3fb7
Merge branch 'main' into dash-interface-query-form
adam-benyekkou Oct 27, 2025
aa011d2
Added dash dependencies to pyproject.toml for CI testing and easier i…
Oct 27, 2025
88432ae
Pulled from remote
Oct 27, 2025
6e0305a
Moved one dependency from dash testing
Oct 27, 2025
d506518
Adding init.py in front folder for tests
Oct 27, 2025
8bef24f
Adding python paath to pyproject.toml for Ci tests
Oct 27, 2025
16b25d8
Merge branch 'mobility-team:main' into main
adam-benyekkou Oct 28, 2025
8e117b7
Merge branch 'main' into dash-interface-query-form
adam-benyekkou Oct 28, 2025
5e94fdb
Merge pull request #11 from adam-benyekkou/dash-interface-query-form
adam-benyekkou Oct 28, 2025
fff8776
Ajout du composant transport_modes_inputs et refactorisation des call…
Oct 28, 2025
acf3408
Ajout du mode de transport covoiturage, et de la sélection des modes …
Oct 29, 2025
6806ad6
Ajout d'une notification lors de la tentative de la désactivation de …
Oct 30, 2025
891a23f
Updated tests for main and added a scenario_service updated test
Nov 12, 2025
224eae0
Merge branch 'main' into dash-interface-transport-modes-inputs
adam-benyekkou Nov 13, 2025
8bb861b
Added back r_script and install_package as in main
Nov 13, 2025
4fa154f
Merge branch 'dash-interface-transport-modes-inputs' of https://githu…
Nov 13, 2025
06cd30d
Added missing comma to pyproject.toml
Nov 13, 2025
b0454c3
Added import uuid to callbacks.py
Nov 13, 2025
809cc1e
Edited tests for CI adding mock input
Nov 13, 2025
6a949b0
Removed non usefull tests that block CI
Nov 13, 2025
156cc09
Ajout de docstring en français en code coté front - généré par IA
Nov 14, 2025
d60cffd
Ajout d'un readme dans le dossier front pour préciser comment lancer …
Nov 14, 2025
e6431be
Merge branch 'main' into dash-interface-transport-modes-inputs
adam-benyekkou Nov 14, 2025
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
30 changes: 0 additions & 30 deletions DESCRIPTION

This file was deleted.

Binary file removed diff.patch
Binary file not shown.
23 changes: 23 additions & 0 deletions front/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Front — Interface Mobility

Ce dossier contient l’interface Dash/Mantine de l’application Mobility.

## Lancer l’interface

Depuis la racine du projet, exécuter :

cd front
python -m app.pages.main.main


L’application démarre alors en mode développement sur http://127.0.0.1:8050/.

## Structure simplifiée

app/components/ — Composants UI (carte, panneaux, contrôles, etc.)

app/services/ — Services pour la génération des scénarios et l’accès aux données

app/pages/main/ — Page principale et point d’entrée (main.py)

app/callbacks.py — Callbacks Dash
File renamed without changes.
4 changes: 4 additions & 0 deletions front/app/components/features/map/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Réexporte Map depuis la nouvelle implémentation.
from .map_component import Map

__all__ = ["Map"]
152 changes: 152 additions & 0 deletions front/app/components/features/map/color_scale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""
color_scale.py
===============

Échelle de couleurs pour la carte des temps moyens de déplacement.

Ce module fournit :
- une palette **bleu → gris → orange** cohérente avec la légende qualitative
(*Accès rapide* / *Accès moyen* / *Accès lent*) ;
- une dataclass `ColorScale` permettant de convertir une valeur numérique
en couleur RGBA (0–255) et d’obtenir un libellé de légende lisible ;
- une fonction d’ajustement `fit_color_scale()` qui calibre automatiquement
`vmin` / `vmax` à partir d’une série de données (percentiles).

Fonctionnalités principales
---------------------------
- `_interp_color(c1, c2, t)` : interpolation linéaire entre deux couleurs RGB.
- `_build_legend_palette(n)` : construit la palette bleu→gris→orange.
- `ColorScale.rgba(v)` : mappe une valeur à un tuple `[R, G, B, A]`.
- `ColorScale.legend(v)` : rend un libellé humain (ex. `"12.3 min"`).
- `fit_color_scale(series)` : ajuste l’échelle à une série pandas (P5–P95).
"""

from dataclasses import dataclass
import numpy as np
import pandas as pd


def _interp_color(c1, c2, t):
"""Interpole linéairement entre deux couleurs RGB.

Args:
c1 (Tuple[int, int, int]): Couleur de départ (R, G, B).
c2 (Tuple[int, int, int]): Couleur d’arrivée (R, G, B).
t (float): Paramètre d’interpolation dans [0, 1].

Returns:
Tuple[int, int, int]: Couleur RGB interpolée.
"""
return (
int(c1[0] + (c2[0] - c1[0]) * t),
int(c1[1] + (c2[1] - c1[1]) * t),
int(c1[2] + (c2[2] - c1[2]) * t),
)


def _build_legend_palette(n=256):
"""Construit une palette bleu → gris → orange pour la légende.

Conçue pour coller à la sémantique :
- bleu : accès rapide (valeurs basses)
- gris : accès moyen (valeurs médianes)
- orange: accès lent (valeurs hautes)

La palette est générée par interpolation linéaire entre
(bleu→gris) puis (gris→orange).

Args:
n (int, optional): Nombre total de couleurs dans la palette.
Par défaut `256`.

Returns:
List[Tuple[int, int, int]]: Liste de couleurs RGB.
"""
blue = ( 74, 160, 205) # accès rapide
grey = (147, 147, 147) # accès moyen
orange = (228, 86, 43) # accès lent

mid = n // 2
first = [_interp_color(blue, grey, i / max(1, mid - 1)) for i in range(mid)]
second = [_interp_color(grey, orange, i / max(1, n - mid - 1)) for i in range(n - mid)]
return first + second


@dataclass
class ColorScale:
"""Échelle de couleurs continue basée sur des bornes min/max.

Attributs:
vmin (float): Valeur minimale du domaine.
vmax (float): Valeur maximale du domaine.
colors (List[Tuple[int, int, int]]): Palette RGB ordonnée bas→haut.
alpha (int): Canal alpha (0–255). Par défaut `102` (~0.4 d’opacité).

Méthodes:
rgba(v): Convertit une valeur en `[R, G, B, A]` (uint8).
legend(v): Produit un libellé humain (ex. `"12.1 min"`).
"""
vmin: float
vmax: float
colors: list[tuple[int, int, int]]
alpha: int = 102 # ~0.4 d’opacité

def rgba(self, v) -> list[int]:
"""Mappe une valeur numérique à une couleur RGBA.

Si `v` est manquante ou si `vmax <= vmin`, retourne une valeur par défaut.

Args:
v (float | Any): Valeur à convertir.

Returns:
List[int]: Couleur `[R, G, B, A]` (chaque canal 0–255).
"""
if v is None or pd.isna(v):
return [200, 200, 200, 40]
if self.vmax <= self.vmin:
idx = 0
else:
t = (float(v) - self.vmin) / (self.vmax - self.vmin)
t = max(0.0, min(1.0, t))
idx = int(t * (len(self.colors) - 1))
r, g, b = self.colors[idx]
return [int(r), int(g), int(b), self.alpha]

def legend(self, v) -> str:
"""Retourne un libellé de légende lisible pour la valeur.

Args:
v (float | Any): Valeur à afficher.

Returns:
str: Libellé, ex. `"12.3 min"`, ou `"N/A"` si manquant.
"""
if v is None or pd.isna(v):
return "N/A"
return f"{float(v):.1f} min"


def fit_color_scale(series: pd.Series) -> ColorScale:
"""Ajuste automatiquement une échelle de couleurs à partir d’une série.

Utilise les percentiles **P5** et **P95** pour définir `vmin` et `vmax`,
afin de diminuer l’influence des valeurs extrêmes.
Si la série est dégénérée (vmin == vmax), retombe sur `(min, max or 1.0)`.
Si la série est vide/invalide, retombe sur le domaine `(0.0, 1.0)`.

Args:
series (pd.Series): Série de valeurs numériques.

Returns:
ColorScale: Échelle prête à l’emploi (palette 256 couleurs, alpha 102).
"""
s = pd.to_numeric(series, errors="coerce").dropna()
if len(s):
vmin = float(np.nanpercentile(s, 5))
vmax = float(np.nanpercentile(s, 95))
if vmin == vmax:
vmin, vmax = float(s.min()), float(s.max() or 1.0)
else:
vmin, vmax = 0.0, 1.0
return ColorScale(vmin=vmin, vmax=vmax, colors=_build_legend_palette(256), alpha=102)
122 changes: 122 additions & 0 deletions front/app/components/features/map/components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
layout.py
=========

Composants de haut niveau pour la page cartographique :
- `DeckMap` : rendu principal Deck.gl (fond de carte + couches)
- `SummaryPanelWrapper` : panneau latéral droit affichant le résumé d’étude
- `ControlsSidebarWrapper` : barre latérale gauche des contrôles de scénario

Ce module assemble des éléments d’UI (Dash + Mantine) et des composants
applicatifs (`StudyAreaSummary`, `ScenarioControlsPanel`) afin de proposer
une mise en page complète : carte plein écran, résumé latéral et sidebar.
"""

import dash_deck
from dash import html
import dash_mantine_components as dmc

from .config import HEADER_OFFSET_PX, SIDEBAR_WIDTH
from app.components.features.study_area_summary import StudyAreaSummary
from app.components.features.scenario_controls import ScenarioControlsPanel

from .tooltip import default_tooltip


def DeckMap(id_prefix: str, deck_json: str) -> dash_deck.DeckGL:
"""Crée le composant Deck.gl plein écran.

Args:
id_prefix (str): Préfixe utilisé pour l’identifiant Dash.
deck_json (str): Spécification Deck.gl sérialisée (JSON) incluant
carte de fond, couches, vues, etc.

Returns:
dash_deck.DeckGL: Composant Deck.gl prêt à l’affichage (pickable, tooltips).
"""
return dash_deck.DeckGL(
id=f"{id_prefix}-deck-map",
data=deck_json,
tooltip=default_tooltip(),
mapboxKey="",
style={
"position": "absolute",
"inset": 0,
"height": "100vh",
"width": "100%",
},
)


def SummaryPanelWrapper(zones_gdf, id_prefix: str):
"""Enveloppe le panneau de résumé global à droite de la carte.

Args:
zones_gdf: GeoDataFrame (ou équivalent) contenant les colonnes utilisées
par `StudyAreaSummary` (temps moyen, parts modales, etc.).
id_prefix (str): Préfixe d’identifiant pour les composants liés à la carte.

Returns:
dash.html.Div: Conteneur du panneau de résumé (`StudyAreaSummary`).
"""
return html.Div(
id=f"{id_prefix}-summary-wrapper",
children=StudyAreaSummary(zones_gdf, visible=True, id_prefix=id_prefix),
)


def ControlsSidebarWrapper(id_prefix: str):
"""Construit la barre latérale gauche contenant les contrôles du scénario.

La sidebar est positionnée sous l’en-tête principal (offset vertical défini
par `HEADER_OFFSET_PX`) et utilise une largeur fixe `SIDEBAR_WIDTH`. Elle
embarque le panneau `ScenarioControlsPanel` (rayon, zone INSEE, modes, bouton).

Args:
id_prefix (str): Préfixe d’identifiant pour éviter les collisions Dash.

Returns:
dash.html.Div: Conteneur sidebar avec un `dmc.Paper` et le panneau de contrôles.
"""
return html.Div(
dmc.Paper(
children=[
dmc.Stack(
[
ScenarioControlsPanel(
id_prefix=id_prefix,
min_radius=15,
max_radius=50,
step=1,
default=40,
default_insee="31555",
)
],
gap="md",
)
],
withBorder=True,
shadow="md",
radius="md",
p="md",
style={
"width": "100%",
"height": "100%",
"overflowY": "auto",
"overflowX": "hidden",
"background": "#ffffffee",
"boxSizing": "border-box",
},
),
id=f"{id_prefix}-controls-sidebar",
style={
"position": "absolute",
"top": f"{HEADER_OFFSET_PX}px",
"left": "0px",
"bottom": "0px",
"width": f"{SIDEBAR_WIDTH}px",
"zIndex": 1200,
"pointerEvents": "auto",
"overflow": "hidden",
},
)
16 changes: 16 additions & 0 deletions front/app/components/features/map/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from dataclasses import dataclass

# ---------- CONSTANTES ----------
CARTO_POSITRON_GL = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
FALLBACK_CENTER = (1.4442, 43.6045) # Toulouse

HEADER_OFFSET_PX = 80
SIDEBAR_WIDTH = 340

# ---------- OPTIONS ----------
@dataclass(frozen=True)
class DeckOptions:
zoom: float = 10
pitch: float = 35
bearing: float = -15
map_style: str = CARTO_POSITRON_GL
Loading
Loading