niamoto


Nameniamoto JSON
Version 0.3.10 PyPI version JSON
download
home_pagehttps://github.com/niamoto/niamoto
SummaryNone
upload_time2025-01-23 22:05:27
maintainerNone
docs_urlNone
authorJulien Barbe
requires_python<4,>=3.11
licenseGPL-3.0-or-later
keywords niamoto
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            
[![PyPI - Version](https://img.shields.io/pypi/v/niamoto.svg)](https://pypi.org/project/niamoto)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/niamoto.svg)](https://pypi.org/project/niamoto)

-----

**Table of Contents**

- [Introduction](#introduction)
- [Installation](#installation)
- [Initial Configuration](#initial-configuration)
- [Development Environment Configuration](#development-environment-configuration)
- [CSV File Format for Import](#csv-file-format-for-import)
- [Niamoto CLI Commands](#niamoto-cli-commands)
  - [Environment Management](#environment-management)
  - [Data Import](#Data-Import)
  - [Statistics Generation](#Statistics-Generation)
  - [Content Generation and Deployment](#content-generation-and-deployment)
- [Niamoto Configuration Overview](#niamoto-configuration-overview)
- [Static Type Checking and Testing with mypy and pytest](#static-type-checking-and-testing-with-mypy-and-pytest)
  - [Using mypy for Static Type Checking](#using-mypy-for-static-type-checking)
  - [Running Tests with pytest](#running-tests-with-pytest)
- [License](#license)
- [Appendix](#appendix)
  - [Complete Configuration Examples](#complete-configuration-examples)

## Introduction

The Niamoto CLI is a tool designed to facilitate the configuration, initialization, and management of data for the Niamoto platform. This tool allows users to configure the database, import data from CSV files, and generate static websites.

## Installation

```console
pip install niamoto
```

## Initial Configuration

After installation, initialize the Niamoto environment using the command:

```
niamoto init
```

This command will create the default configuration necessary for Niamoto to operate. Use the `--reset` option to reset the environment if it already exists.


## Development Environment Configuration

To set up a development environment for Niamoto, you must have `Poetry` installed on your system. Poetry is a dependency management and packaging tool for Python.

1. **Poetry Installation**:

  To install Poetry, run the following command:

  ```bash
  curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
  ```

2. **Clone the Niamoto repository**:

  Clone the Niamoto repository on your system using `git`:

  ```bash
  git clone https://github.com/niamoto/niamoto.git
  ```

3. **Configure the development environment with Poetry**:

  Move into the cloned directory and install the dependencies with Poetry:
  ```bash
  cd niamoto
  poetry install
  ```

4. **Activate the virtual environment**:

  Activate the virtual environment created by Poetry:
  ```bash
  poetry shell
  ```

5. **Editable Installation**:

    If you want to install the project in editable mode (i.e., source code changes are immediately reflected without needing to reinstall the package), you can use the following command:

```console
pip install -e .
```

## CSV File Format for Taxonomy Import

To import taxonomic data into Niamoto, you must provide a structured CSV file with the following columns:

| Column         | Description                                           |
|----------------|-------------------------------------------------------|
| `id_taxon`     | Unique identifier of the taxon                        |
| `full_name`    | Full name of the taxon                                |
| `rank_name`    | Taxonomic rank (e.g., family, genus, species)         |
| `id_family`    | Identifier of the family to which the taxon belongs   |
| `id_genus`     | Identifier of the genus to which the taxon belongs    |
| `id_species`   | Identifier of the species to which the taxon belongs  |
| `id_infra`     | Infraspecific identifier of the taxon                 |
| `authors`      | Authors of the taxon name                             |


### Niamoto CLI Commands

This section describes the command-line interface (CLI) commands available in Niamoto for managing your environment, importing data, transforming data, exporting content, and deploying.

#### 1. Initialize or Check Your Environment

```bash
# Initialize the environment (or check the current status)
$ niamoto init

# Reinitialize the database and remove generated files in the outputs directory
$ niamoto init --reset
```

#### 2. Import Data

```bash
# Import taxonomy data
$ niamoto import taxonomy <file>

# Import plot data
$ niamoto import plots <file>

# Import occurrence data
$ niamoto import occurrences <file>

# Import all sources defined in the configuration
$ niamoto import
```

#### 3. Transform Data

```bash
# Transform data by taxon
$ niamoto transform --group taxon

# Transform data by plot
$ niamoto transform --group plot

# Transform data by shape
$ niamoto transform --group shape

# Transform for all groups
$ niamoto transform
```

#### 4. Export Content

```bash
# Export static pages by taxon
$ niamoto export pages --group taxon

# Export static pages by plot
$ niamoto export pages --group plot

# Export static pages by shape
$ niamoto export pages --group shape

# Export for all groups
$ niamoto export
```

#### 5. Deploy Content

```bash
# Deploy to GitHub Pages
$ niamoto deploy github --repo <url>

# Deploy to Netlify
$ niamoto deploy netlify --site-id <id>
```


## Project Structure

```config/``` - YAML configuration files for data pipeline:
- `config.yml`: Global configuration options
- `import.yml`: Data source definitions (CSV, vector, raster)  
- `transform.yml`: Data transformation rules and calculations
- `export.yml`: Widget and chart configurations

```db/``` - Database files and schemas

```exports/``` - Generated widget data and statistics

```imports/``` - Raw data files (CSV, shapefiles, rasters)

```logs/``` - Application logs and debug information

## Niamoto Configuration Overview

Niamoto uses **three** primary YAML files to handle data ingestion, stats calculation, and page presentation:

1. **`import.yml`** (Data Sources): 
   - Lists and describes **where** to fetch the raw data (CSV paths, database tables, shapefiles, rasters, etc.).  
   - Each source can specify type (`csv`, `vector`, `raster`), path, and any special parameters (identifiers, location fields, etc.).  

2. **`transform.yml`** (Data Calculations): 
   - Describes **what** computations or transformations to perform for each `group_by` (e.g., `taxon`, `plot`, `shape`).
   - Each block of `widgets_data` in the config defines **one output JSON field** (i.e., “widget data”) in the final stats table.
   - Transformations can be things like `count`, `bins`, `top`, `assemble_info`, or custom logic to produce aggregated results.

3. **`export.yml`** (Widgets & Charts): 
   - Defines **which widgets** appear on the final pages and how they look (chart options, color schemes, labels, etc.).
   - Points to the JSON fields produced by the `import` via `source: my_widget_field`.
   - Contains chart.js–style configurations (datasets, labels, axes, legends, etc.).

By splitting these responsibilities, Niamoto provides a more modular, maintainable, and scalable system.

---

### 1) `import.yml` – Data Sources

The **data configuration** file focuses on **where** each data source resides and how to interpret it.  
Typical structure might look like:

```yaml
  # Example data sources
  taxonomy:
    type: "csv"
    path: "data/sources/amap_data_taxa.csv"
    ranks: "id_famille,id_genre,id_espèce,id_sous-espèce"

  plots:
    type: "vector"          # e.g., a GeoPackage
    path: "data/sources/plots.gpkg"
    identifier: "id_locality"
    location_field: "geometry"

  occurrences:
    type: "csv"
    path: "data/sources/amap_data_occurrences.csv"
    identifier: "id_taxonref"
    location_field: "geo_pt"

  # Possibly multi-shapes
  shapes:
    - category: "provinces"
      type: "vector"
      path: "data/sources/shapes/provinces.zip"
      name_field: "nom"
      label: "Provinces"

  # Raster layers
  layers:
    - name: "forest_cover"
      type: "vector"
      path: "data/sources/layers/forest_cover.shp"
    - name: "elevation"
      type: "raster"
      path: "data/sources/layers/mnt100.tif"
```

### Key Points

- `type: "csv" | "vector" | "raster"` helps Niamoto decide which loader to use.  
- Additional fields like `identifier`, `location_field`, or `name_field` specify how the data is keyed or geo-located.  
- This config does **not** define transformations—just **data definitions** and **paths**.

---

### 2) `transform.yml` – Calculations & Transformations

The **stats configuration** replaces the older notion of a single `fields:` mapping with a more flexible concept of **widgets_data**:

- **`group_by`:** Identifies the entity type (e.g., `"taxon"`, `"plot"`, `"shape"`).  
- **`identifier`:** The unique ID field for that entity.  
- **`widgets_data`:** A dictionary (or list) of “widget names” (e.g., `general_info`, `dbh_distribution`) where each widget yields **one JSON** field in the final stats table.  
- **`transformations`:** A list of steps used to compute or aggregate data for that widget.

### Example

```yaml
  - group_by: "taxon"
    identifier: "id_taxonref"
    source_table_name: "occurrences"

    widgets_data:
      general_info:
        transformations:
          - name: "collect_fields"
            fields:
              - { source_field: "taxon_name", key: "name" }
              - { transformation: "count", source: "occurrences", key: "occurrences_count" }
              # etc.

      dbh_distribution:
        transformations:
          - name: "bins"
            source_field: "dbh"
            bins: [10, 20, 30, 40, 50, 100]
            # Produces a JSON with bins & counts

      dbh_gauge:
        transformations:
          - name: "max_value"
            source_field: "dbh"
            # E.g., { "value": 210, "max": 500 }
```

Niamoto (or your stats calculator classes) will:

1. Load data from the sources defined in `import.yml`.  
2. For each widget in `widgets_data`, run the transformations.  
3. Insert/Update a table named `{group_by}_stats` (e.g. `taxon_stats`) storing each widget’s output in a JSON field named after the widget (e.g. `general_info`, `dbh_distribution`, etc.).

---

### 3) `export.yml` – Visualization

Lastly, the **presentation configuration** describes each **widget**’s **visual layout**:

- For a **given** `group_by`, we list multiple **widgets** (e.g., `general_info`, `map_panel`, `forest_cover`…).
- Each widget has:
  - **`type:`** – `bar_chart`, `doughnut_chart`, `gauge`, `info_panel`, `map_panel`, etc.
  - **`source:`** – The JSON field created by `stats_config`.  
  - Chart.js–style `datasets:`, `options:` for advanced styling.

### Example

```yam
  - group_by: "taxon"
    widgets:
      general_info:
        type: "info_panel"
        title: "Taxon Information"
        layout: grid
        fields:
          - source: "name"
            label: "Taxon Name"
          - source: "occurrences_count"
            label: "Occurrences"
      dbh_distribution:
        type: "bar_chart"
        title: "DBH Distribution"
        source: "dbh_distribution"
        datasets:
          - label: "Count"
            data_key: "counts"
        labels_key: "bins"
        options:
          indexAxis: "x"
```

**Key Takeaway**: The front-end or page generator uses these details to render each widget with the data from the corresponding JSON field (`source: dbh_distribution`).

---

## Special Fields & Transformations

### Calculated Fields

- Use transformations like `"count"`, `"mean"`, `"top"`, `"bins"` etc. in `stats_config`.
- They produce a JSON result stored in a single widget field.

### Boolean Fields

- A transformation can produce an object like `{ true: X, false: Y }`, which you can display with a pie chart, for instance.

### Geographical Fields

- A transformation `"coordinates"` or `"geometry_coords"` can produce a set of features.  
- The `presentation_config` might specify a `map_panel` widget referencing that JSON.

### Bins & Distribution

- If you want to discretize data (DBH, altitude, rainfall), use `"bins"` in `stats_config`.
- The resulting JSON (e.g., `{ bins: [...], counts: [...] }`) becomes the data for a bar or line chart in `presentation_config`.

---

## Summary

1. **`import.yml`** – Where are the raw data sources? (CSV, shapefile, DB table, etc.)  
2. **`transform.yml`** – For each `group_by` (taxon, shape, plot), define `widgets_data` with transformations. Each widget becomes a JSON column in `_group_by` (taxon, shape, plot) table.  
3. **`export.yml`** – For each widget, define how it’s displayed (chart type, datasets, axes, etc.), referencing the JSON column by `source:`.

This **cleanly decouples** data sources, data calculations, and final presentation. You can **change** any of these layers independently:

- Modify a chart’s color or title? → `export`.
- Add a new computed field (like “mean DBH per species”)? → `transform`.
- Point Niamoto to a new CSV or raster? → `import`.

## Static Type Checking and Testing with mypy and pytest

### Using mypy for Static Type Checking

[mypy](http://mypy-lang.org/) is an optional static type checker for Python that aims to combine the benefits of dynamic (duck) typing and static typing. It checks the type annotations in your Python code to find common bugs as soon as possible during the development cycle.

To run mypy on your code:

```bash
mypy src/niamoto
```

### Running Tests with pytest

[pytest](https://docs.pytest.org/) is a framework that makes it easy to write simple tests, yet scales to support complex functional testing for applications and libraries.

To run your tests with pytest, use:

```bash
pytest --cov=src --cov-report html
```

## Documentation

The documentation for the Niamoto CLI tool is available in the `docs` directory. It includes information on the CLI commands, configuration options, and data import formats.

To build the documentation, you can use the following command:

```bash
cd docs
sphinx-apidoc -o . ../src/niamoto
make html
make markdown
```

## License

`niamoto` is distributed under the terms of the [GPL-3.0-or-later](https://spdx.org/licenses/GPL-3.0-or-later.html) license.


## Appendix

### Complete Configuration Examples
This appendix provides complete examples of the three main configuration files 
used in Niamoto: config.yml, sources.yml, stats.yml, and presentation.yml.
Complete Example 1: Species Distribution Analysis

### config.yml   
```yaml
database:
  path: db/niamoto.db
logs:
  path: logs
exports:
  web: exports # web root directory
  api: exports/api # api root directory
  files: exports/files # static files directory
```

### import.yml
```yaml
# 1) The main basic "entities"
taxonomy:
  type: csv
  path: "imports/taxonomy.csv"
  ranks: "id_famille,id_genre,id_espèce,id_sous-espèce"

plots:
  type: vector
  format: geopackage
  path: "imports/plots.gpkg"
  identifier: "id_locality"
  location_field: "geometry"

occurrences:
  type: csv
  path: "imports/occurrences.csv"
  identifier: "id_taxonref"
  location_field: "geo_pt"

occurrence_plots:
  type: "csv"
  path: "imports/occurrence-plots.csv"
  role: "link"               
  left_key: "id_occurrence"  
  right_key: "id_plot"

# 2) Multiple shapes (administrative areas, substrates, etc.)
shapes:
  - category: "provinces"
    type: vector
    format: directory_shapefiles
    path: "imports/shapes/provinces"
    name_field: "nom"
    label: "Provinces"
    description: "Administrative boundaries of the provinces"

  - category: "communes"
    type: vector
    format: directory_shapefiles
    path: "imports/shapes/communes"
    name_field: "nom"
    label: "Communes"
    description: "Administrative boundaries of the communes"

  - category: "protected_areas"
    type: vector
    format: directory_shapefiles
    path: "imports/shapes/protected_areas"
    name_field: "libelle"
    label: "Aires protégées"
    description: "Protected areas"

  - category: "substrates"
    type: vector
    format: geopackage
    path: "imports/shapes/substrate.gpkg"
    name_field: "label"
    label: "Substrats"
    description: "Substrate types"

  - category: "holdridge"
    type: vector
    format: geopackage
    path: "imports/shapes/holdridge_zones.gpkg"
    name_field: "zone"
    label: "Zone de vie"
    description: "Holdridge life zones"

  - category: "water_catchments"
    type: vector
    format: directory_shapefiles
    path: "imports/shapes/ppe"
    name_field: "nom_ppe"
    label: "Captage"
    description: "Water catchment areas"

  - category: "mines"
    type: vector
    format: geopackage
    path: "imports/shapes/mines.gpkg"
    name_field: "region"
    label: "Emprises Minières"
    description: "Mining sites"

# 3) Layers: vectors, rasters...
layers:
  - name: "forest_cover"
    type: vector
    format: shapefile
    path: "imports/layers/amap_carto_3k_20240715/amap_carto_3k_20240715.shp"
    description: "Forest cover layer"

  - name: "elevation"
    type: raster
    path: "imports/layers/mnt100_epsg3163.tif"
    description: "Digital elevation model"

  - name: "rainfall"
    type: raster
    path: "imports/layers/rainfall_epsg3163.tif"
    description: "Annual rainfall distribution"

  - name: "holdridge"
    type: raster
    path: "imports/layers/amap_raster_holdridge_nc.tif"
    description: "Holdridge"
```

### transform.yml
```yaml
##################################################################
# 1) CONFIG POUR LES TAXONS
##################################################################
- group_by: taxon
  identifier: id_taxonref
  source_table_name: occurrences
  source_location_field: geo_pt
  reference_table_name: taxon_ref
  
  widgets_data:
    general_info:
      # On veut un champ JSON "general_info" qui combine plusieurs informations 
      # (ex: nom du taxon, rang, nombre d'occurrences…)
      transformations:
        - name: collect_fields
          items:
            - source: taxonomy
              field: full_name
              key: "name"
            - source: taxonomy
              field: rank_name
              key: "rank"
            - source: occurrences
              transformation: count
              key: "occurrences_count"

    distribution_map:
      # Doit contenir les coordonnées des occurrences, prêtes à être affichées
      # Sous forme d'un GeoJSON ou d'un array de points.
      transformations:
        - name: coordinates
          source: occurrences
          source_field: geo_pt

    top_species:
      transformations:
        - name: top
          source: occurrences
          target_ranks: ["espèce", "sous-espèce"]
          count: 10
          # ex: { "labels": [...], "values": [...], ... }

    distribution_substrat:
      # "Ultramafique vs Non‐UM"
      transformations:
        - name: count_bool
          source_field: in_um
          # ex: { "um": 30, "num": 50, ... }

    phenology_distribution:
      # Fleurs / Fruits par mois
      transformations:
        - name: temporal_phenology
          source_fields:
            fleur: flower
            fruit: fruit
          time_field: month_obs
          # ex: structure un objet : { "month_data": [...], "colors": {...} }

    dbh_distribution:
      # Distribution diamétrique + histogramme
      transformations:
        - name: dbh_bins
          source_field: dbh
          bins: [10, 20, 30, 40, 50, 75, 100, 200, 300, 400, 500]
          # ex: { "bins": [...], "counts": [...] }

    dbh:
      # Diamètre max (exemple)
      transformations:
        - name: max_value
          source_field: dbh
          units: "cm"
          max_value: 500
          # ex: { "value": 120, "max": 500, "units": "cm" }

    height_max:
      # Hauteur max
      transformations:
        - name: max_value
          source_field: height
          units: "m"
          max_value: 40
          # ex: { "value": 35, "max": 40, "units": "m" }

    elevation_distribution:
      # Bins altitudinaux
      transformations:
        - name: histogram
          source_field: elevation
          bins: [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1700]
          # ex: { "bins": [...], "counts": [...] }

    rainfall_distribution:
      transformations:
        - name: histogram
          source_field: rainfall
          bins: [1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000]

    holdridge_distribution:
      transformations:
        - name: histogram
          source_field: holdridge
          bins: [1, 2, 3]
          labels: ["Sec", "Humide", "Très humide"]

    strata_distribution:
      transformations:
        - name: histogram
          source_field: strata
          bins: [1, 2, 3, 4]
          labels: ["Sous-bois", "Sous-Canopée", "Canopée", "Emergent"]

    wood_density:
      # On veut la densité moyenne & extrêmes par ex.
      transformations:
        - name: stats_min_mean_max
          source_field: wood_density
          units: "g/cm3"
          max_value: 1.2
          # ex: { "mean": 0.65, "max": 1.2, "min": 0.4 }

    bark_thickness:
      transformations:
        - name: stats_min_mean_max
          source_field: bark_thickness
          units: "mm"
          max_value: 80

    leaf_sla:
      transformations:
        - name: stats_min_mean_max
          source_field: leaf_sla
          units: "g/m2"
          max_value: 50

    leaf_area:
      transformations:
        - name: stats_min_mean_max
          source_field: leaf_area
          units: "cm2"
          max_value: 1500

    leaf_thickness:
      transformations:
        - name: stats_min_mean_max
          source_field: leaf_thickness
          units: "µm"
          max_value: 800
##################################################################
# 2) CONFIG POUR LES PLOTS
##################################################################
- group_by: plot
  identifier: id_source
  source_table_name: occurrences
  reference_table_name: plot_ref
  pivot_table_name: occurrences_plots
  filter: 
    field: source
    value: occ_ncpippn

  widgets_data:

    general_info:
      transformations:
        - name: collect_fields
          items:
            - source: plot_ref
              field: locality 
              key: "name"
            - source: plots
              field: elevation
              key: "elevation"
              units: "m"
            - source: plots
              field: rainfall
              key: "rainfall"
              units: "mm/an"
            - source: plots
              field: holdridge
              key: "holdridge"
              labels: ["Sec", "Humide", "Très humide"]
            - source: plots
              field: substrat
              key: "substrat"
              labels: 
                UM: "Substrat ultramafique"
                VS: "Substrat non ultramafique"
            - source: plots
              field: nb_families
              key: "nb_families"
            - source: plots
              field: nb_species
              key: "nb_species"
            - source: occurrences
              transformation: count
              key: "occurrences_count"

    map_panel:
      # Localisation de la parcelle
      transformations:
        - name: get_geometry
          source: plots
          source_field: geometry
        # ex: un JSON "map" : { "coordinates": [...], "label": ... }

    top_families:
      transformations:
        - name: top
          source: occurrences
          target_ranks: ["famille"]
          count: 10
        # ex: { "labels": [...], "values": [...] }

    top_species:
      transformations:
        - name: top
          source: occurrences
          target_ranks: ["espèce", "sous-espèce"]
          count: 10
        # ex: { "labels": [...], "values": [...] }
        

    dbh_distribution:
      transformations:
        - name: dbh_bins
          source_field: dbh
          bins: [10, 20, 30, 40, 50, 75, 100, 200, 300, 400, 500]
        # ex: { "bins": [...], "counts": [...], ... }

    strata_distribution:
      transformations:
        - name: histogram
          source_field: strata
          bins: [1, 2, 3, 4]
          labels: ["Sous-Bois", "Sous-Canopée", "Canopée", "Emergent"]
        # ex: { "bins": [...], "counts": [...], ... }

    height:
      transformations:
        - name: mean_value
          source_field: height
          units: "m"
          max_value: 40
        # ex: { "value": 25, "max": 40, "units": "m" }

    wood_density:
      transformations:
        - name: mean_value
          source_field: wood_density
          units: "g/cm3"
          max_value: 1.2
        # ex: { "value": 0.65, "max": 1.2, "units": "g/cm3" }

    basal_area:
      transformations:
        - name: identity_value
          source: plots
          source_field: basal_area
          units: "m²/ha"
          max_value: 100
        # ex: { "value": 25, "max": 100, "units": "m²/ha" }

    richness:
      transformations:
        - name: identity_value
          source: plots
          source_field: nb_species
          max_value: 50
        # ex: { "value": 25, "max": 50, "units": "" }

    shannon:
      transformations:
        - name: identity_value
          source: plots
          source_field: shannon
          max_value: 5
        # ex: { "value": 2.5, "max": 5, "units": "" }

    pielou:
      transformations:
        - name: identity_value
          source: plots
          source_field: pielou
          max_value: 1
        # ex: { "value": 0.5, "max": 1, "units": "" }

    simpson:
      transformations:
        - name: identity_value
          source: plots
          source_field: simpson
          max_value: 1
        # =>ex:{ "value": 0.5, "max": 1, "units": "" }
        

    biomass:
      transformations:
        - name: identity_value
          source: plots
          source_field: biomass
          units: "t/ha"
          max_value: 800
##################################################################
# 3) CONFIG POUR LES SHAPES
##################################################################
- group_by: shape
  occurrence_location_field: geo_pt
  raw_data: imports/row_shape_stats.csv
  identifier: "id"

  widgets_data:

    # 1) CHAMP "general_info"
    #    On assemble plusieurs champs (surface_totale, forest_area, pluviometrie, altitude, etc.)
    general_info:
      transformations:
        - name: collect_fields
          # À implémenter de manière générique.
          # On récupère dans row_shape_stats plusieurs class_object (land_area_ha, forest_area_ha, etc.)
          items:
            - source: shape_ref
              field: label
              key: "name"
            - source: raw_data
              class_object: "land_area_ha"
              key: "land_area_ha"
              units: "ha"
              format: "number"     
            - source: raw_data
              class_object: "forest_area_ha"
              key: "forest_area_ha"
              units: "ha"
              format: "number"
            - source: raw_data
              class_object: "forest_mining_ha"
              key: "forest_mining_ha"
              units: "ha"
              format: "number"
            - source: raw_data
              class_object: "forest_reserve_ha"
              key: "forest_reserve_ha"
              units: "ha"
              format: "number"
            - source: raw_data
              class_object: "forest_ppe_ha"
              key: "forest_ppe_ha"
              units: "ha"
              format: "number"
            - source: raw_data
              class_object: ["rainfall_min", "rainfall_max"]
              key: "rainfall"
              units: "mm/an"
              format: "range"
            - source: raw_data
              class_object: "elevation_median"
              key: "elevation_median"
              units: "m"
              format: "number"
            - source: raw_data
              class_object: "elevation_max"
              key: "elevation_max"
              units: "m"
              format: "number"
              # => ex. {"label": "PROVINCE NORD", "land_area_ha": 941252.41325591, "forest_area_ha": 321711.765827868, "forest_mining_ha": 21106, "forest_reserve_ha": 11800, "forest_ppe_ha": 48275, "rainfall": {"min": 510, "max": 4820}, "elevation_median": 214, "elevation_max": 1622}

    # 2) CHAMP "geography"
    #    On peut inclure la géométrie du shape + la géométrie "forest cover" si besoin
    geography:
      transformations:
        - name: geometry_coords
          # Filtrer le shape_gdf pour en extraire shape_coords
          # Filtrer forest_gdf pour en extraire forest_coords
          # Renvoyer un JSON { "shape_coords": ..., "forest_coords": ... }

    # 3) CHAMP "forest_cover"
    #    On veut faire un "pie" ou un "doughnut" => typiquement, on va chercher
    #    class_object = "cover_forest", "cover_forestum", "cover_forestnum"
    forest_cover:
      transformations:
        - name: extract_multi_class_object
          # Dans row_shape_stats, on a:
          #   cover_forest     => Forêt / Hors-Forêt
          #   cover_forestum   => Forêt / Hors-Forêt
          #   cover_forestnum  => Forêt / Hors-Forêt
          # L’idée est de regrouper ces 3 distributions dans un unique JSON, ex.:
          # {
          #   "emprise": { "forêt": 0.34, "hors_foret": 0.66 },
          #   "um": { "forêt": 0.23, "hors_foret": 0.77 },
          #   "num": { "forêt": 0.37, "hors_foret": 0.63 }
          # }
          params:
            # On peut paramétrer lister les "class_object" qu'on veut extraire
            - label: "emprise"
              class_object: "cover_forest"
            - label: "um"
              class_object: "cover_forestum"
            - label: "num"
              class_object: "cover_forestnum"
          # Ton code itère sur param et lit row_shape_stats[(class_object == ...)] 
          # pour "Forêt" et "Hors-forêt".

    # 4) CHAMP "land_use"
    land_use:
      transformations:
        - name: extract_by_class_object
          class_object: "land_use"
          categories_order: 
            - "NUM"
            - "UM"
            - "Sec"
            - "Humide"
            - "Très Humide"
            - "Réserve"
            - "PPE"
            - "Concessions"
            - "Forêt"
          # => parse la table row_shape_stats pour class_object = "land_use"
          # => {"categories": ["NUM", "UM", "Sec", "Humide", "Tr\u00e8s Humide", "R\u00e9serve", "PPE", "Concessions", "For\u00eat"], "values": [720516.368203804, 220736.045052106, 245865.633017676, 564601.884540423, 130784.895697811, 14272.8699792226, 94334.7084348428, 121703.503624097, 321711.765827868]}

    # 5) CHAMP "elevation_distribution"
    elevation_distribution:
      transformations:
        - name: extract_elevation_distribution
          # Dedans, tu peux soit réutiliser la fonction "calculate_elevation_distribution",
          # soit lire row_shape_stats "forest_elevation" / "land_elevation" / ...
          # L'idée : renvoyer un JSON type:
          # {
          #   "altitudes": [100,200,...,1700],
          #   "forest": [...],
          #   "non_forest": [...]
          # }

    # 6) CHAMP "holdridge"
    holdridge:
      transformations:
        - name: extract_holdridge
          # Ce param mappera holdridge_forest (Sec/Humide/Très Humide)
          # => ex. {"forest": {"sec": 0.0222477792523791, "humide": 0.2235085776628972, "tres_humide": 0.0960348154600766}, "non_forest": {"sec": 0.2389633789397109, "humide": 0.3763325240320183, "tres_humide": 0.0429129246529177}}

    # 7) CHAMP "forest_types"
    forest_types:
      transformations:
        - name: extract_by_class_object
          class_object: "cover_foresttype"
          # => ex. { "Forêt coeur": 0.064..., "Forêt mature": 0.51..., "Forêt secondaire": 0.42... }

    # 8) CHAMP "forest_cover_by_elevation"
    forest_cover_by_elevation:
      transformations:
        - name: extract_elevation_matrix
          # On peut imaginer qu’on lit forest_elevation, forest_um_elevation, forest_num_elevation,
          # => ex. { "altitudes": [...], "um": [...], "num": [...], "hors_foret_um": [...], "hors_foret_num": [...] }

    # 9) CHAMP "forest_types_by_elevation"
    forest_types_by_elevation:
      transformations:
        - name: extract_forest_types_by_elevation
          # Va lire:
          #   forest_secondary_elevation
          #   forest_mature_elevation
          #   forest_core_elevation
          # pour assembler un JSON:
          # {
          #   "altitudes": [...],
          #   "secondaire": [...],
          #   "mature": [...],
          #   "coeur": [...]
          # }

    # 10) CHAMP "fragmentation"
    fragmentation:
      transformations:
        - name: collect_fields
          # On veut juste lire 'fragment_meff_cbc' => ex. 189.98456266
          items:
            - source: raw_data
              class_object: fragment_meff_cbc
              key: "meff"

    # 11) CHAMP "fragmentation_distribution"
    fragmentation_distribution:
      transformations:
        - name: extract_distribution
          class_object: "forest_fragmentation"
          # => Sur row_shape_stats, on a "forest_fragmentation" + class_name = [10, 20, 30...], class_value = ...
          # => Produire { "sizes": [...], "values": [...] }
```

### export.yml
```yaml
##################################################################
# 1) PRÉSENTATION POUR LES TAXONS
##################################################################
- group_by: taxon
  widgets:
    general_info:
      type: info_panel
      title: "Informations générales"
      layout: grid
      fields:
        - source: rank
          label: "Rang"
        - source: occurrences_count
          label: "Nombre d'occurrences"
          format: "number"
      # => Ton JSON "general_info" pourrait contenir { "taxon_name": "...", "occurrences_count": 123, ... }

    distribution_map:
      type: map_panel
      title: "Distribution géographique"
      description: Distribution géographique des occurrences du taxon et de ses sous-taxons
      source: distribution_map
      layout: full_width
      layers:
        - id: "occurrences"
          source: coordinates
          style:
            color: "#1fb99d"
            weight: 1
            fillColor: "#00716b"
            fillOpacity: 0.5
            radius: 2000
      # => JSON { "coordinates": [...], "style": {...} } (selon le format produit)

    top_species:
      type: bar_chart
      title: "Sous-taxons principaux"
      description: "Principaux sous-taxons (espèce, sous-espèce)"
      source: top_species
      sortData: true
      datasets:
        - label: 'Occurrences'
          data_key: counts
          generateColors: true 
      labels_key: tops
      options:
        indexAxis: 'y'
        scales:
          x:
            beginAtZero: true
            grid: {
              display: true,
              drawBorder: true,
              drawOnChartArea: true,
              drawTicks: true
            }
            ticks: {
              stepSize: 5
            }
            title:
              display: true
              text: "Nombre d'occurrences"
          y:
            grid: {
              display: false
            }
        plugins:
          legend: {
            display: false
          }
        maintainAspectRatio: false
        responsive: true
      # => ex. JSON { "tops": [...], "counts": [...] }

    dbh_distribution:
      type: bar_chart
      title: "Distribution diamétrique (DBH)"
      description: Répartition des occurrences par classe de diamètre
      source: dbh_distribution
      datasets:
        - label: "Occurrences"
          data_key: "counts"
          backgroundColor: "#4CAF50"
      labels_key: "bins"
      options:
        scales:
          y:
            title:
              display: true
              text: "Nombre d'occurrences"
          x:
            title:
              display: true
              text: "DBH (cm)"

    phenology_distribution:
      type: line_chart   
      title: "Phénologie"
      description: Phénologie des états fertiles (fleur, fruit) du taxon et de ses sous-taxons
      source: phenology_distribution
      datasets:
        - label: "Fleur"
          data_key: "month_data.fleur"
          borderColor: "#FF9800"    
          backgroundColor: "transparent"
          tension: 0.4
          pointRadius: 4
        - label: "Fruit"
          data_key: "month_data.fruit"
          borderColor: "#4CAF50"    
          backgroundColor: "transparent"
          tension: 0.4
          pointRadius: 4
      labels: ["Jan", "Fev", "Mar", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
      options:
        scales:
          y:
            title:
              display: true
              text: "Fréquence (%)"

    distribution_substrat:
      type: doughnut_chart
      title: "Distribution substrat"
      description: "Distribution des occurrences par substrat (= fréquence du taxon par substrat)"
      source: distribution_substrat
      datasets:
        - label: 'Distribution substrat'
          data_keys: ['um', 'num']
          backgroundColor: ['#b08d57', '#78909c']
          borderColor: '#ffffff'
          borderWidth: 2
      labels: ['Ultramafique (UM)', 'non-Ultramafique (NUM)']
      options:
        cutout: '1%'
        plugins:
          legend:
            display: true
            position: 'top'
            align: 'center'
            labels:
              usePointStyle: false
              padding: 20
              boxWidth: 30
              color: '#666666'
              font:
                size: 12
        layout:
          padding:
            top: 20

    holdridge_distribution:
      type: bar_chart
      title: "Milieu de vie"
      description: Fréquence des occurrences du taxon et de ses sous-taxons par milieu de vie
      source: holdridge_distribution
      datasets:
        - label: "Occurrences"
          data_key: "counts"
          backgroundColor: ["#8B0000", "#FFEB3B", "#1E88E5"]  # Rouge, Jaune, Bleu
      labels_key: "labels"
      options:
        scales:
          y:
            title:
              display: true
              text: "Altitude (m)"

    rainfall_distribution:
      type: bar_chart
      title: "Répartition pluviométrie"
      description: Distribution pluviométrique des occurrences du taxon (= fréquence par classe de pluviométrie)
      source: rainfall_distribution
      datasets:
        - label: "Occurrences"
          data_key: "counts"
          backgroundColor: "#2196F3"    # Bleu comme dans l'image
      labels_key: "bins"
      options:
        indexAxis: "y"
        scales:
          x:
            title:
              display: true
              text: "Occurrences"
          y:
            title:
              display: true
              text: "Pluviométrie (mm/an)"

    strata_distribution:
      type: bar_chart
      title: "Stratification"
      description: Répartition des occurrences par strate
      source: strata_distribution
      datasets:
        - label: "Occurrences"
          data_key: "counts"
          backgroundColor: ["#90A4AE", "#66BB6A", "#43A047", "#2E7D32"]  # Du plus clair au plus foncé
          borderWidth: 1
      labels_key: "labels"
      options:
        indexAxis: 'y'
        scales:
          x:
            title:
              display: true
              text: "Nombre d'occurrences"

    height_max:
      type: gauge
      title: "Hauteur maximale"
      description: Hauteur maximale atteint par le taxon et ses sous-taxons
      source: height_max
      value_key: "value"
      options:
        min: 0
        max: 40
        units: "m"
        sectors:
          - color: '#f02828'
            range: [0, 10]
          - color: '#fe6a00'
            range: [10, 18]
          - color: '#e8dd0f'
            range: [18, 25]
          - color: '#81e042'
            range: [25, 33]
          - color: '#049f50'
            range: [33, 40]

    wood_density:
      type: gauge
      title: "Densité de bois"
      description: Densité de bois moyenne mesuré avec une Tarière de Pressler
      source: wood_density
      value_key: "mean"
      options:
        min: 0
        max: 1.2
        units: "g/cm³"
        sectors:
          - color: '#f02828'
            range: [0.000, 0.240]
          - color: '#fe6a00'
            range: [0.240, 0.480]
          - color: '#e8dd0f'
            range: [0.480, 0.720]
          - color: '#81e042'
            range: [0.720, 0.960]
          - color: '#049f50'
            range: [0.960, 1.200]

    bark_thickness:
      type: gauge
      title: "Épaisseur d'écorce"
      description: Epaisseur moyenne de l'écorce mesurée à la jauge à écorce
      source: bark_thickness
      value_key: "mean"
      options:
        min: 0
        max: 80
        units: "mm"
        sectors:
          - color: '#f02828'
            range: [0, 16]
          - color: '#fe6a00'
            range: [16, 32]
          - color: '#e8dd0f'
            range: [32, 48]
          - color: '#81e042'
            range: [48, 64]
          - color: '#049f50'
            range: [64, 80]

    leaf_sla:
      type: gauge
      title: "Surface foliaire spécifique"
      description: Surface foliaire spécifique du taxon et de ses sous-taxons
      source: leaf_sla
      value_key: "mean"
      options:
        min: 0
        max: 50
        units: "m²·kg⁻¹"
        sectors:
          - color: '#f02828'
            range: [0, 10]
          - color: '#fe6a00'
            range: [10, 20]
          - color: '#e8dd0f'
            range: [20, 30]
          - color: '#81e042'
            range: [30, 40]
          - color: '#049f50'
            range: [40, 50]

    leaf_area:
      type: gauge
      title: "Surface foliaire"
      description: Surface foliaire du taxon et de ses sous-taxons
      source: leaf_area
      value_key: "mean"
      options:
        min: 0
        max: 1500
        units: "cm²"
        sectors:
          - color: '#f02828'
            range: [0, 300]
          - color: '#fe6a00'
            range: [300, 600]
          - color: '#e8dd0f'
            range: [600, 900]
          - color: '#81e042'
            range: [900, 1200]
          - color: '#049f50'
            range: [1200, 1500]

    leaf_thickness:
      type: gauge
      title: "Épaisseur des feuilles"
      description: Epaisseur moyenne des feuilles du taxon et de ses sous-taxons
      source: leaf_thickness
      value_key: "mean"
      options:
        min: 0
        max: 800
        units: "µm"
        sectors:
          - color: '#f02828'
            range: [0, 160]
          - color: '#fe6a00'
            range: [160, 320]
          - color: '#e8dd0f'
            range: [320, 480]
          - color: '#81e042'
            range: [480, 640]
          - color: '#049f50'
            range: [640, 800]
##################################################################
# 2) PRÉSENTATION POUR LES PLOTS
##################################################################
- group_by: plot
  widgets:
    general_info:
      type: info_panel
      title: "Informations générales"
      layout: grid
      fields:
        - source: elevation
          label: "altitudes"
        - source: rainfall
          label: "Précipitation annuelle moyenne"
        - source: holdridge
          label: "Milieu de vie"
        - source: substrat
          label: "Substrat"
        - source: nb_families
          label: "Nombre de familles"
        - source: nb_species
          label: "Nombre d'espèces"
        - source: occurrences_count
          label: "Nombre d'occurrences"
          format: "number"

      # => ex. { "plot_name": "Parcelle A", "basal_area": 25.3, "trees_count": 364, ... }

    map_panel:
      type: map_panel
      title: "Localisation de la parcelle"
      source: map_panel
      layout: full_width
      layers:
        - id: "plot"
          source: geometry
          style:
            color: "#1fb99d"
            weight: 2
            fillOpacity: 0
      # => ex. JSON { "coordinates": [...], "style": {...} }

    top_families:
      type: bar_chart
      title: "Familles dominantes"
      description: "Les dix familles botaniques les plus fréquentes de la parcelle"
      source: top_families
      sortData: true
      datasets:
        - label: 'Occurrences'
          data_key: counts
          generateColors: true 
      labels_key: tops
      options:
        indexAxis: 'y'
        scales:
          x:
            beginAtZero: true
            grid: {
              display: true,
              drawBorder: true,
              drawOnChartArea: true,
              drawTicks: true
            }
            ticks: {
              stepSize: 5
            }
            title:
              display: true
              text: "Nombre d'occurrences"
          y:
            grid: {
              display: false
            }
        plugins:
          legend: {
            display: false
          }
        maintainAspectRatio: false
        responsive: true
      # => ex. JSON { "labels": [...], "values": [...], ... }

    top_species:
      type: bar_chart
      title: "Sous-taxons principaux"
      description: "Les dix espèces botaniques les plus fréquentes de la parcelle"
      source: top_species
      sortData: true
      datasets:
        - label: 'Occurrences'
          data_key: counts
          generateColors: true 
      labels_key: tops
      options:
        indexAxis: 'y'
        scales:
          x:
            beginAtZero: true
            grid: {
              display: true,
              drawBorder: true,
              drawOnChartArea: true,
              drawTicks: true
            }
            ticks: {
              stepSize: 5
            }
            title:
              display: true
              text: "Nombre d'occurrences"
          y:
            grid: {
              display: false
            }
        plugins:
          legend: {
            display: false
          }
        maintainAspectRatio: false
        responsive: true
      # => ex. JSON { "tops": [...], "counts": [...] }

    dbh_distribution:
      type: bar_chart
      title: "Distribution diamétrique (DBH)"
      description: Répartition des occurrences par classe de diamètre
      source: dbh_distribution
      datasets:
        - label: "Occurrences"
          data_key: "counts"
          backgroundColor: "#4CAF50"
      labels_key: "bins"
      options:
        scales:
          y:
            title:
              display: true
              text: "Nombre d'occurrences"
          x:
            title:
              display: true
              text: "DBH (cm)"

    strata_distribution:
      type: bar_chart
      title: "Stratification"
      description: Répartition des occurrences par strate
      source: strata_distribution
      datasets:
        - label: "Occurrences"
          data_key: "counts"
          backgroundColor: ["#90A4AE", "#66BB6A", "#43A047", "#2E7D32"]  # Du plus clair au plus foncé
          borderWidth: 1
      labels_key: "labels"
      options:
        indexAxis: 'y'
        scales:
          x:
            title:
              display: true
              text: "Nombre d'occurrences"

    height:
      type: gauge
      title: "Hauteur moyenne"
      source: height
      value_key: "value"
      options:
        min: 0
        max: 40
        units: "m"
        sectors:
          - color: '#f02828'
            range: [0, 5]
          - color: '#fe6a00'
            range: [5, 10]
          - color: '#e8dd0f'
            range: [10, 15]
          - color: '#81e042'
            range: [15, 20]
          - color: '#049f50'
            range: [20, 25]

    wood_density:
      type: gauge
      title: "Densité de bois"
      description: Densité de bois moyenne mesuré avec une Tarière de Pressler
      source: wood_density
      value_key: "value"
      options:
        min: 0
        max: 1.2
        units: "g/cm³"
        sectors:
          - color: '#f02828'
            range: [0.000, 0.240]
          - color: '#fe6a00'
            range: [0.240, 0.480]
          - color: '#e8dd0f'
            range: [0.480, 0.720]
          - color: '#81e042'
            range: [0.720, 0.960]
          - color: '#049f50'
            range: [0.960, 1.200]

    basal_area:
      type: gauge
      title: "Aire basale"
      source: basal_area
      value_key: "value"
      options:
        min: 0
        max: 100
        units: "m²/ha"
        sectors:
          - color: '#f02828'
            range: [0, 15]
          - color: '#fe6a00'
            range: [15, 30]
          - color: '#e8dd0f'
            range: [30, 45]
          - color: '#81e042'
            range: [45, 60]
          - color: '#049f50'
            range: [60, 76]

    richness:
      type: gauge
      title: "Richesse"
      source: richness
      value_key: "value"
      options:
        min: 0
        max: 130
        units: ""
        sectors:
          - color: '#f02828'
            range: [0, 28]
          - color: '#fe6a00'
            range: [28, 52]
          - color: '#e8dd0f'
            range: [52, 78]
          - color: '#81e042'
            range: [78, 104]
          - color: '#049f50'
            range: [104, 130]
    shannon:
      type: gauge
      title: "Shannon"
      source: shannon
      value_key: "value"
      options:
        min: 0
        max: 5
        units: ""
        sectors:
          - color: '#f02828'
            range: [0.0, 1.0]
          - color: '#fe6a00'
            range: [1.0, 2.0]
          - color: '#e8dd0f'
            range: [2.0, 3.0]
          - color: '#81e042'
            range: [3.0, 4.0]
          - color: '#049f50'
            range: [4.0, 5.0]

    pielou:
      type: gauge
      title: "Pielou"
      source: pielou
      value_key: "value"
      options:
        min: 0
        max: 1
        units: ""
        sectors:
          - color: '#f02828'
            range: [0.00, 0.20]
          - color: '#fe6a00'
            range: [0.20, 0.40]
          - color: '#e8dd0f'
            range: [0.40, 0.60]
          - color: '#81e042'
            range: [0.60, 0.80]
          - color: '#049f50'
            range: [0.80, 1.0]

    simpson:
      type: gauge
      title: "Simpson"
      source: simpson
      value_key: "value"
      options:
        min: 0
        max: 1
        units: ""
        sectors:
          - color: '#f02828'
            range: [0.00, 0.20]
          - color: '#fe6a00'
            range: [0.20, 0.40]
          - color: '#e8dd0f'
            range: [0.40, 0.60]
          - color: '#81e042'
            range: [0.60, 0.80]
          - color: '#049f50'
            range: [0.80, 1.0]

    biomass:
      type: gauge
      title: "Biomasse"
      source: biomass
      value_key: "value"
      options:
        min: 0
        max: 800
        units: "t/ha"
        sectors:
          - color: '#f02828'
            range: [0, 160]
          - color: '#fe6a00'
            range: [160, 320]
          - color: '#e8dd0f'
            range: [320, 480]
          - color: '#81e042'
            range: [480, 640]
          - color: '#049f50'
            range: [640, 800]
      
##################################################################
# 3) PRÉSENTATION POUR LES SHAPES
##################################################################
- group_by: shape
  widgets:
    general_info:
      type: info_panel
      title: "Informations générales"
      layout: grid
      fields:
        - source: land_area_ha
          label: "Surface totale"
          suffix: " ha"
          format: "number"
        - source: forest_area_ha
          label: "Surface forêt"
          suffix: " ha"
          format: "number"
        - source: forest_mining_ha
          label: "Forêt sur mine"
          suffix: " ha"
          format: "number"
        - source: forest_reserve_ha
          label: "Forêt en réserve"
          suffix: " ha"
          format: "number"
        - source: forest_ppe_ha
          label: "Forêt sur captage (PPE)"
          suffix: " ha"
          format: "number"
        - source: rainfall
          label: "Pluviométrie"
          format: "range"
          suffix: " mm/an"
        - source: elevation_median
          label: "Altitude médiane"
          format: "number"
          suffix: " m"
        - source: elevation_max
          label: "Altitude maximale"
          format: "number"
          suffix: " m"

    map_panel:
      type: map_panel
      title: "Distribution de la forêt"
      description: "Distribution de la forêt dans l'emprise sélectionnée"
      source: geography
      layers:
        - id: shape
          source: geography.shape_coords
          style:
            color: "#1fb99d"
            weight: 2
            fillOpacity: 0
        - id: forest
          source: geography.forest_coords
          style:
            color: "#228b22"
            weight: 0.3
            fillColor: "#228b22cc"
            fillOpacity: 0.8

    forest_cover:
      type: doughnut_chart
      title: "Couverture forestière"
      description: "La couverture forestière (= superficie de forêt / superficie disponible) est un indicateur de l'importance de la forêt dans le paysage."
      source: forest_cover
      datasets:
        - label: 'Emprise'
          data_keys: ['emprise.foret', 'emprise.hors_foret']
          transformData: 'toPercentage'
          backgroundColors: ['#2E7D32', '#F4E4BC']
          borderColor: '#ffffff'
          borderWidth: 2
        - label: 'NUM'
          data_keys: ['num.foret', 'num.hors_foret']
          transformData: 'toPercentage'
          backgroundColors: ['#2E7D32', '#C5A98B']
          borderWidth: 2
          borderColor: '#ffffff'
        - label: 'UM'
          data_keys: ['um.foret', 'um.hors_foret']
          transformData: 'toPercentage'
          backgroundColors: ['#2E7D32', '#8B7355']
          borderColor: '#ffffff'
          borderWidth: 2
      labels: ['Forêt', 'Hors-forêt']
      options:
        cutout: '20%'
        rotation: -90
        plugins:
          legend:
            display: false
          tooltip:
            enabled: false
      customPlugin: 'customLabels'

    land_use:
      type: bar_chart
      title: "Occupation du sol"
      description: "Superficie occupée par le substrat, les milieux de vie de Holdridge, les limites administratives et la forêt dans l'emprise sélectionnée"
      source: land_use
      datasets:
        - label: 'Occupation du sol'
          data_key: 'values'
          color_mapping:
            NUM: "#8B4513"
            UM: "#CD853F"
            Sec: "#8B0000"
            Humide: "#FFEB3B"
            "Très Humide": "#1E88E5"
            Réserve: "#4CAF50"
            PPE: "#90CAF9"
            Concessions: "#E57373"
            Forêt: "#2E7D32"
      labels_key: 'categories'
      options:
        indexAxis: 'x'
        scales:
          x:
            title:
              display: true
              text: ''
          y:
            title:
              display: true
              text: 'Superficie (ha)'
            ticks:
              callback: 'formatSurfaceValue'

    elevation_distribution:
      type: bar_chart
      title: "Distribution altitudinale"
      description: "Distribution altitudinale de la forêt dans l'emprise"
      source: elevation_distribution
      datasets:
        - label: 'Forêt'
          data_key: 'forest'
          backgroundColor: '#2E7D32'
          stack: 'Stack 0'
        - label: 'Hors-forêt'
          data_key: 'non_forest'
          backgroundColor: '#F4E4BC'
          stack: 'Stack 0'
      labels_key: 'altitudes'
      options:
        indexAxis: 'y'
        scales:
          x:
            title:
              display: true
              text: 'Superficie (ha)'
          y:
            reverse: true
            title:
              display: true
              text: 'Altitude (m)'

    holdridge_distribution:
      type: bar_chart
      title: "Forêt et milieux de vie"
      description: "Distribution de la forêt selon les milieux de vie de Holdridge"
      source: holdridge
      datasets:
        - label: 'Forêt'
          transformData: 'toPercentage'
          data_keys: ['forest.sec', 'forest.humide', 'forest.tres_humide']
          backgroundColor: '#2E7D32'
          stack: 'Stack 0'
        - label: 'Hors-forêt'
          transformData: 'toPercentage'
          data_keys: ['non_forest.sec', 'non_forest.humide', 'non_forest.tres_humide']
          backgroundColor: '#F4E4BC'
          stack: 'Stack 0'
      labels: ['Sec', 'Humide', 'Très humide']
      options:
        indexAxis: 'x'
        scales:
          x:
            title:
              display: true
              text: 'Type de milieu'
          y:
            title:
              display: true
              text: 'Proportion (%)'

    forest_types:
      type: doughnut_chart
      title: "Types forestiers"
      description: "Répartition de la forêt selon les trois types de forêt"
      source: forest_types
      customPlugin: 'forestTypeLabelsPlugin'
      datasets:
        - label: 'Types de forêt'
          data_key: 'values'
          transformData: 'toPercentage'
          backgroundColor: ['#2E7D32', '#7CB342', '#C5E1A5']
          borderWidth: 2
          borderColor: '#ffffff'
      labels_key: 'categories'  # Utilise les catégories du JSON comme labels
      options:
        cutout: '60%'
        plugins:
          legend:
            position: 'bottom'
            labels:
              padding: 20
              font:
                size: 12
              usePointStyle: false
              boxWidth: 15
          tooltip:
            callbacks:
              label: 'formatForestTypeTooltip'

    forest_cover_by_elevation:
      type: bar_chart
      title: "Couverture forestière par altitude"
      description: "Distribution altitudinale de la couverture forestière en fonction du substrat"
      source: forest_cover_by_elevation
      datasets:
        - label: 'Forêt (UM)'
          data_key: 'um'
          backgroundColor: '#90EE90'
          stack: 'Stack 0'
          transformData: 'negateValues'
        - label: 'Forêt (NUM)'
          data_key: 'num'
          backgroundColor: '#2E7D32'
          stack: 'Stack 0'
      labels_key: 'altitudes'
      options:
        responsive: true
        maintainAspectRatio: false
        indexAxis: 'y'
        scales:
          x:
            stacked: true
            position: 'top'
            min: -100
            max: 100
            grid:
              lineWidth: 1
              drawTicks: false
              borderDash: [5, 5]
            ticks:
              callback: 'formatAbsoluteValue'
              stepSize: 20
              autoSkip: false
              maxRotation: 0
            border:
              display: false
          y:
            stacked: true
            position: 'left'
            reverse: true
            grid:
              display: true
              lineWidth: 1
              drawTicks: false
              borderDash: [5, 5]
            ticks:
              font:
                size: 12
            title:
              display: true
              text: 'Altitude (m)'
              font:
                size: 12
            border:
              display: false
        plugins:
          legend:
            position: 'bottom'
            align: 'center'
            labels:
              boxWidth: 10
              padding: 15
          title:
            display: true
            text: 'Couverture (%)'
            position: 'top'
            align: 'center'
          tooltip:
            mode: 'y'
            intersect: false
            callbacks:
              label: 'formatForestCoverTooltip'

    forest_types_by_elevation:
      type: line_chart
      title: "Types forestiers par altitude"
      description: "Distribution des types de forêts selon l'altitude"
      source: forest_types_by_elevation
      sortBy: 'altitudes'
      datasets:
        - label: 'Forêt secondaire'
          data_key: 'secondaire'
          transformData: 'stackedPercentage'
          backgroundColor: '#C5E1A5'
          borderColor: '#C5E1A5'
          fill: true
          pointStyle: 'circle'
          pointRadius: 0
          pointHoverRadius: 5
          pointHoverBackgroundColor: '#ffffff'
          tension: 0.4
          stack: 'Stack 0'
        - label: 'Forêt mature'
          data_key: 'mature'
          transformData: 'stackedPercentage'
          backgroundColor: '#7CB342'
          borderColor: '#7CB342'
          fill: true
          pointStyle: 'circle'
          pointRadius: 0
          pointHoverRadius: 5
          pointHoverBackgroundColor: '#ffffff'
          tension: 0.4
          stack: 'Stack 0'
        - label: 'Forêt de coeur'
          data_key: 'coeur'
          transformData: 'stackedPercentage'
          backgroundColor: '#2E7D32'
          borderColor: '#2E7D32'
          fill: true
          pointStyle: 'circle'
          pointRadius: 0
          pointHoverRadius: 5
          pointHoverBackgroundColor: '#ffffff'
          tension: 0.4
          stack: 'Stack 0'
      labels_key: 'altitudes'
      options:
        responsive: true
        maintainAspectRatio: false
        scales:
          x:
            title:
              display: true
              text: 'Altitude (m)'
              font:
                size: 12
            grid:
              display: false
            ticks:
              maxRotation: 0
          y:
            stacked: true
            grid:
              color: '#e5e5e5'
              borderDash: [2, 2]
            ticks:
              callback: 'formatPercentage'
            title:
              display: true
              text: 'Fréquence (%)'
              font:
                size: 12
            min: 0
            max: 100
        plugins:
          legend:
            position: 'bottom'
            labels:
              padding: 20
              usePointStyle: false
              boxWidth: 15
          tooltip:
            mode: 'index'
            intersect: false
            callbacks:
              label: 'formatForestTypeElevationTooltip'
        interaction:
          mode: 'nearest'
          axis: 'x'
          intersect: false

    fragmentation:
      type: gauge
      title: "Fragmentation"
      description: "La taille effective de maillage représente la probabilité que deux points appartiennent au même fragment de forêt"
      source: fragmentation
      value_key: 'meff'
      options:
        min: 0
        max: 1000
        units: 'km²'
        sectors:
          - color: '#f02828'
            range: [0, 200]
          - color: '#fe6a00'
            range: [200, 400]
          - color: '#e8dd0f'
            range: [400, 600]
          - color: '#81e042'
            range: [600, 800]
          - color: '#049f50'
            range: [800, 1000]

    fragmentation_distribution:
      type: line_chart
      title: "Fragments forestiers"
      description: "Aire cumulée de chaque fragment forestier classé du plus petit au plus grand"
      source: fragmentation_distribution
      sortData: true
      sortBy: 'sizes'
      datasets:
        - label: 'Aire Cumulée'
          data_key: 'values'
          backgroundColor: '#2E7D32'
          borderColor: '#2E7D32'
          fill: true
          tension: 0.3
          pointRadius: 0
          borderWidth: 2
          transformData: 'toPercentage'
      labels_key: 'sizes'
      options:
        scales:
          x:
            type: 'logarithmic'
            title:
              display: true
              text: 'Surface (ha)'
            grid:
              display: false
          y:
            title:
              display: true
              text: 'Fréquence (%)'
            grid:
              color: '#e5e5e5'
              borderDash: [2, 2]
            ticks:
              callback: 'formatPercentage'
            min: 0
            max: 100
            beginAtZero: true
        plugins:
          legend:
            position: 'bottom'
            labels:
              usePointStyle: false
              boxWidth: 15
              padding: 20
```
            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/niamoto/niamoto",
    "name": "niamoto",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4,>=3.11",
    "maintainer_email": null,
    "keywords": "niamoto",
    "author": "Julien Barbe",
    "author_email": "julien.barbe@me.com",
    "download_url": "https://files.pythonhosted.org/packages/76/88/12c0ecca1667c051276d1edac436a694fcdacad95ae18c1594f6b5eac15f/niamoto-0.3.10.tar.gz",
    "platform": null,
    "description": "\n[![PyPI - Version](https://img.shields.io/pypi/v/niamoto.svg)](https://pypi.org/project/niamoto)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/niamoto.svg)](https://pypi.org/project/niamoto)\n\n-----\n\n**Table of Contents**\n\n- [Introduction](#introduction)\n- [Installation](#installation)\n- [Initial Configuration](#initial-configuration)\n- [Development Environment Configuration](#development-environment-configuration)\n- [CSV File Format for Import](#csv-file-format-for-import)\n- [Niamoto CLI Commands](#niamoto-cli-commands)\n  - [Environment Management](#environment-management)\n  - [Data Import](#Data-Import)\n  - [Statistics Generation](#Statistics-Generation)\n  - [Content Generation and Deployment](#content-generation-and-deployment)\n- [Niamoto Configuration Overview](#niamoto-configuration-overview)\n- [Static Type Checking and Testing with mypy and pytest](#static-type-checking-and-testing-with-mypy-and-pytest)\n  - [Using mypy for Static Type Checking](#using-mypy-for-static-type-checking)\n  - [Running Tests with pytest](#running-tests-with-pytest)\n- [License](#license)\n- [Appendix](#appendix)\n  - [Complete Configuration Examples](#complete-configuration-examples)\n\n## Introduction\n\nThe Niamoto CLI is a tool designed to facilitate the configuration, initialization, and management of data for the Niamoto platform. This tool allows users to configure the database, import data from CSV files, and generate static websites.\n\n## Installation\n\n```console\npip install niamoto\n```\n\n## Initial Configuration\n\nAfter installation, initialize the Niamoto environment using the command:\n\n```\nniamoto init\n```\n\nThis command will create the default configuration necessary for Niamoto to operate. Use the `--reset` option to reset the environment if it already exists.\n\n\n## Development Environment Configuration\n\nTo set up a development environment for Niamoto, you must have `Poetry` installed on your system. Poetry is a dependency management and packaging tool for Python.\n\n1. **Poetry Installation**:\n\n  To install Poetry, run the following command:\n\n  ```bash\n  curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -\n  ```\n\n2. **Clone the Niamoto repository**:\n\n  Clone the Niamoto repository on your system using `git`:\n\n  ```bash\n  git clone https://github.com/niamoto/niamoto.git\n  ```\n\n3. **Configure the development environment with Poetry**:\n\n  Move into the cloned directory and install the dependencies with Poetry:\n  ```bash\n  cd niamoto\n  poetry install\n  ```\n\n4. **Activate the virtual environment**:\n\n  Activate the virtual environment created by Poetry:\n  ```bash\n  poetry shell\n  ```\n\n5. **Editable Installation**:\n\n    If you want to install the project in editable mode (i.e., source code changes are immediately reflected without needing to reinstall the package), you can use the following command:\n\n```console\npip install -e .\n```\n\n## CSV File Format for Taxonomy Import\n\nTo import taxonomic data into Niamoto, you must provide a structured CSV file with the following columns:\n\n| Column         | Description                                           |\n|----------------|-------------------------------------------------------|\n| `id_taxon`     | Unique identifier of the taxon                        |\n| `full_name`    | Full name of the taxon                                |\n| `rank_name`    | Taxonomic rank (e.g., family, genus, species)         |\n| `id_family`    | Identifier of the family to which the taxon belongs   |\n| `id_genus`     | Identifier of the genus to which the taxon belongs    |\n| `id_species`   | Identifier of the species to which the taxon belongs  |\n| `id_infra`     | Infraspecific identifier of the taxon                 |\n| `authors`      | Authors of the taxon name                             |\n\n\n### Niamoto CLI Commands\n\nThis section describes the command-line interface (CLI) commands available in Niamoto for managing your environment, importing data, transforming data, exporting content, and deploying.\n\n#### 1. Initialize or Check Your Environment\n\n```bash\n# Initialize the environment (or check the current status)\n$ niamoto init\n\n# Reinitialize the database and remove generated files in the outputs directory\n$ niamoto init --reset\n```\n\n#### 2. Import Data\n\n```bash\n# Import taxonomy data\n$ niamoto import taxonomy <file>\n\n# Import plot data\n$ niamoto import plots <file>\n\n# Import occurrence data\n$ niamoto import occurrences <file>\n\n# Import all sources defined in the configuration\n$ niamoto import\n```\n\n#### 3. Transform Data\n\n```bash\n# Transform data by taxon\n$ niamoto transform --group taxon\n\n# Transform data by plot\n$ niamoto transform --group plot\n\n# Transform data by shape\n$ niamoto transform --group shape\n\n# Transform for all groups\n$ niamoto transform\n```\n\n#### 4. Export Content\n\n```bash\n# Export static pages by taxon\n$ niamoto export pages --group taxon\n\n# Export static pages by plot\n$ niamoto export pages --group plot\n\n# Export static pages by shape\n$ niamoto export pages --group shape\n\n# Export for all groups\n$ niamoto export\n```\n\n#### 5. Deploy Content\n\n```bash\n# Deploy to GitHub Pages\n$ niamoto deploy github --repo <url>\n\n# Deploy to Netlify\n$ niamoto deploy netlify --site-id <id>\n```\n\n\n## Project Structure\n\n```config/``` - YAML configuration files for data pipeline:\n- `config.yml`: Global configuration options\n- `import.yml`: Data source definitions (CSV, vector, raster)  \n- `transform.yml`: Data transformation rules and calculations\n- `export.yml`: Widget and chart configurations\n\n```db/``` - Database files and schemas\n\n```exports/``` - Generated widget data and statistics\n\n```imports/``` - Raw data files (CSV, shapefiles, rasters)\n\n```logs/``` - Application logs and debug information\n\n## Niamoto Configuration Overview\n\nNiamoto uses **three** primary YAML files to handle data ingestion, stats calculation, and page presentation:\n\n1. **`import.yml`** (Data Sources): \n   - Lists and describes **where** to fetch the raw data (CSV paths, database tables, shapefiles, rasters, etc.).  \n   - Each source can specify type (`csv`, `vector`, `raster`), path, and any special parameters (identifiers, location fields, etc.).  \n\n2. **`transform.yml`** (Data Calculations): \n   - Describes **what** computations or transformations to perform for each `group_by` (e.g., `taxon`, `plot`, `shape`).\n   - Each block of `widgets_data` in the config defines **one output JSON field** (i.e., \u201cwidget data\u201d) in the final stats table.\n   - Transformations can be things like `count`, `bins`, `top`, `assemble_info`, or custom logic to produce aggregated results.\n\n3. **`export.yml`** (Widgets & Charts): \n   - Defines **which widgets** appear on the final pages and how they look (chart options, color schemes, labels, etc.).\n   - Points to the JSON fields produced by the `import` via `source: my_widget_field`.\n   - Contains chart.js\u2013style configurations (datasets, labels, axes, legends, etc.).\n\nBy splitting these responsibilities, Niamoto provides a more modular, maintainable, and scalable system.\n\n---\n\n### 1) `import.yml` \u2013 Data Sources\n\nThe **data configuration** file focuses on **where** each data source resides and how to interpret it.  \nTypical structure might look like:\n\n```yaml\n  # Example data sources\n  taxonomy:\n    type: \"csv\"\n    path: \"data/sources/amap_data_taxa.csv\"\n    ranks: \"id_famille,id_genre,id_esp\u00e8ce,id_sous-esp\u00e8ce\"\n\n  plots:\n    type: \"vector\"          # e.g., a GeoPackage\n    path: \"data/sources/plots.gpkg\"\n    identifier: \"id_locality\"\n    location_field: \"geometry\"\n\n  occurrences:\n    type: \"csv\"\n    path: \"data/sources/amap_data_occurrences.csv\"\n    identifier: \"id_taxonref\"\n    location_field: \"geo_pt\"\n\n  # Possibly multi-shapes\n  shapes:\n    - category: \"provinces\"\n      type: \"vector\"\n      path: \"data/sources/shapes/provinces.zip\"\n      name_field: \"nom\"\n      label: \"Provinces\"\n\n  # Raster layers\n  layers:\n    - name: \"forest_cover\"\n      type: \"vector\"\n      path: \"data/sources/layers/forest_cover.shp\"\n    - name: \"elevation\"\n      type: \"raster\"\n      path: \"data/sources/layers/mnt100.tif\"\n```\n\n### Key Points\n\n- `type: \"csv\" | \"vector\" | \"raster\"` helps Niamoto decide which loader to use.  \n- Additional fields like `identifier`, `location_field`, or `name_field` specify how the data is keyed or geo-located.  \n- This config does **not** define transformations\u2014just **data definitions** and **paths**.\n\n---\n\n### 2) `transform.yml` \u2013 Calculations & Transformations\n\nThe **stats configuration** replaces the older notion of a single `fields:` mapping with a more flexible concept of **widgets_data**:\n\n- **`group_by`:** Identifies the entity type (e.g., `\"taxon\"`, `\"plot\"`, `\"shape\"`).  \n- **`identifier`:** The unique ID field for that entity.  \n- **`widgets_data`:** A dictionary (or list) of \u201cwidget names\u201d (e.g., `general_info`, `dbh_distribution`) where each widget yields **one JSON** field in the final stats table.  \n- **`transformations`:** A list of steps used to compute or aggregate data for that widget.\n\n### Example\n\n```yaml\n  - group_by: \"taxon\"\n    identifier: \"id_taxonref\"\n    source_table_name: \"occurrences\"\n\n    widgets_data:\n      general_info:\n        transformations:\n          - name: \"collect_fields\"\n            fields:\n              - { source_field: \"taxon_name\", key: \"name\" }\n              - { transformation: \"count\", source: \"occurrences\", key: \"occurrences_count\" }\n              # etc.\n\n      dbh_distribution:\n        transformations:\n          - name: \"bins\"\n            source_field: \"dbh\"\n            bins: [10, 20, 30, 40, 50, 100]\n            # Produces a JSON with bins & counts\n\n      dbh_gauge:\n        transformations:\n          - name: \"max_value\"\n            source_field: \"dbh\"\n            # E.g., { \"value\": 210, \"max\": 500 }\n```\n\nNiamoto (or your stats calculator classes) will:\n\n1. Load data from the sources defined in `import.yml`.  \n2. For each widget in `widgets_data`, run the transformations.  \n3. Insert/Update a table named `{group_by}_stats` (e.g. `taxon_stats`) storing each widget\u2019s output in a JSON field named after the widget (e.g. `general_info`, `dbh_distribution`, etc.).\n\n---\n\n### 3) `export.yml` \u2013 Visualization\n\nLastly, the **presentation configuration** describes each **widget**\u2019s **visual layout**:\n\n- For a **given** `group_by`, we list multiple **widgets** (e.g., `general_info`, `map_panel`, `forest_cover`\u2026).\n- Each widget has:\n  - **`type:`** \u2013 `bar_chart`, `doughnut_chart`, `gauge`, `info_panel`, `map_panel`, etc.\n  - **`source:`** \u2013 The JSON field created by `stats_config`.  \n  - Chart.js\u2013style `datasets:`, `options:` for advanced styling.\n\n### Example\n\n```yam\n  - group_by: \"taxon\"\n    widgets:\n      general_info:\n        type: \"info_panel\"\n        title: \"Taxon Information\"\n        layout: grid\n        fields:\n          - source: \"name\"\n            label: \"Taxon Name\"\n          - source: \"occurrences_count\"\n            label: \"Occurrences\"\n      dbh_distribution:\n        type: \"bar_chart\"\n        title: \"DBH Distribution\"\n        source: \"dbh_distribution\"\n        datasets:\n          - label: \"Count\"\n            data_key: \"counts\"\n        labels_key: \"bins\"\n        options:\n          indexAxis: \"x\"\n```\n\n**Key Takeaway**: The front-end or page generator uses these details to render each widget with the data from the corresponding JSON field (`source: dbh_distribution`).\n\n---\n\n## Special Fields & Transformations\n\n### Calculated Fields\n\n- Use transformations like `\"count\"`, `\"mean\"`, `\"top\"`, `\"bins\"` etc. in `stats_config`.\n- They produce a JSON result stored in a single widget field.\n\n### Boolean Fields\n\n- A transformation can produce an object like `{ true: X, false: Y }`, which you can display with a pie chart, for instance.\n\n### Geographical Fields\n\n- A transformation `\"coordinates\"` or `\"geometry_coords\"` can produce a set of features.  \n- The `presentation_config` might specify a `map_panel` widget referencing that JSON.\n\n### Bins & Distribution\n\n- If you want to discretize data (DBH, altitude, rainfall), use `\"bins\"` in `stats_config`.\n- The resulting JSON (e.g., `{ bins: [...], counts: [...] }`) becomes the data for a bar or line chart in `presentation_config`.\n\n---\n\n## Summary\n\n1. **`import.yml`** \u2013 Where are the raw data sources? (CSV, shapefile, DB table, etc.)  \n2. **`transform.yml`** \u2013 For each `group_by` (taxon, shape, plot), define `widgets_data` with transformations. Each widget becomes a JSON column in `_group_by` (taxon, shape, plot) table.  \n3. **`export.yml`** \u2013 For each widget, define how it\u2019s displayed (chart type, datasets, axes, etc.), referencing the JSON column by `source:`.\n\nThis **cleanly decouples** data sources, data calculations, and final presentation. You can **change** any of these layers independently:\n\n- Modify a chart\u2019s color or title? \u2192 `export`.\n- Add a new computed field (like \u201cmean DBH per species\u201d)? \u2192 `transform`.\n- Point Niamoto to a new CSV or raster? \u2192 `import`.\n\n## Static Type Checking and Testing with mypy and pytest\n\n### Using mypy for Static Type Checking\n\n[mypy](http://mypy-lang.org/) is an optional static type checker for Python that aims to combine the benefits of dynamic (duck) typing and static typing. It checks the type annotations in your Python code to find common bugs as soon as possible during the development cycle.\n\nTo run mypy on your code:\n\n```bash\nmypy src/niamoto\n```\n\n### Running Tests with pytest\n\n[pytest](https://docs.pytest.org/) is a framework that makes it easy to write simple tests, yet scales to support complex functional testing for applications and libraries.\n\nTo run your tests with pytest, use:\n\n```bash\npytest --cov=src --cov-report html\n```\n\n## Documentation\n\nThe documentation for the Niamoto CLI tool is available in the `docs` directory. It includes information on the CLI commands, configuration options, and data import formats.\n\nTo build the documentation, you can use the following command:\n\n```bash\ncd docs\nsphinx-apidoc -o . ../src/niamoto\nmake html\nmake markdown\n```\n\n## License\n\n`niamoto` is distributed under the terms of the [GPL-3.0-or-later](https://spdx.org/licenses/GPL-3.0-or-later.html) license.\n\n\n## Appendix\n\n### Complete Configuration Examples\nThis appendix provides complete examples of the three main configuration files \nused in Niamoto: config.yml, sources.yml, stats.yml, and presentation.yml.\nComplete Example 1: Species Distribution Analysis\n\n### config.yml   \n```yaml\ndatabase:\n  path: db/niamoto.db\nlogs:\n  path: logs\nexports:\n  web: exports # web root directory\n  api: exports/api # api root directory\n  files: exports/files # static files directory\n```\n\n### import.yml\n```yaml\n# 1) The main basic \"entities\"\ntaxonomy:\n  type: csv\n  path: \"imports/taxonomy.csv\"\n  ranks: \"id_famille,id_genre,id_esp\u00e8ce,id_sous-esp\u00e8ce\"\n\nplots:\n  type: vector\n  format: geopackage\n  path: \"imports/plots.gpkg\"\n  identifier: \"id_locality\"\n  location_field: \"geometry\"\n\noccurrences:\n  type: csv\n  path: \"imports/occurrences.csv\"\n  identifier: \"id_taxonref\"\n  location_field: \"geo_pt\"\n\noccurrence_plots:\n  type: \"csv\"\n  path: \"imports/occurrence-plots.csv\"\n  role: \"link\"               \n  left_key: \"id_occurrence\"  \n  right_key: \"id_plot\"\n\n# 2) Multiple shapes (administrative areas, substrates, etc.)\nshapes:\n  - category: \"provinces\"\n    type: vector\n    format: directory_shapefiles\n    path: \"imports/shapes/provinces\"\n    name_field: \"nom\"\n    label: \"Provinces\"\n    description: \"Administrative boundaries of the provinces\"\n\n  - category: \"communes\"\n    type: vector\n    format: directory_shapefiles\n    path: \"imports/shapes/communes\"\n    name_field: \"nom\"\n    label: \"Communes\"\n    description: \"Administrative boundaries of the communes\"\n\n  - category: \"protected_areas\"\n    type: vector\n    format: directory_shapefiles\n    path: \"imports/shapes/protected_areas\"\n    name_field: \"libelle\"\n    label: \"Aires prot\u00e9g\u00e9es\"\n    description: \"Protected areas\"\n\n  - category: \"substrates\"\n    type: vector\n    format: geopackage\n    path: \"imports/shapes/substrate.gpkg\"\n    name_field: \"label\"\n    label: \"Substrats\"\n    description: \"Substrate types\"\n\n  - category: \"holdridge\"\n    type: vector\n    format: geopackage\n    path: \"imports/shapes/holdridge_zones.gpkg\"\n    name_field: \"zone\"\n    label: \"Zone de vie\"\n    description: \"Holdridge life zones\"\n\n  - category: \"water_catchments\"\n    type: vector\n    format: directory_shapefiles\n    path: \"imports/shapes/ppe\"\n    name_field: \"nom_ppe\"\n    label: \"Captage\"\n    description: \"Water catchment areas\"\n\n  - category: \"mines\"\n    type: vector\n    format: geopackage\n    path: \"imports/shapes/mines.gpkg\"\n    name_field: \"region\"\n    label: \"Emprises Mini\u00e8res\"\n    description: \"Mining sites\"\n\n# 3) Layers: vectors, rasters...\nlayers:\n  - name: \"forest_cover\"\n    type: vector\n    format: shapefile\n    path: \"imports/layers/amap_carto_3k_20240715/amap_carto_3k_20240715.shp\"\n    description: \"Forest cover layer\"\n\n  - name: \"elevation\"\n    type: raster\n    path: \"imports/layers/mnt100_epsg3163.tif\"\n    description: \"Digital elevation model\"\n\n  - name: \"rainfall\"\n    type: raster\n    path: \"imports/layers/rainfall_epsg3163.tif\"\n    description: \"Annual rainfall distribution\"\n\n  - name: \"holdridge\"\n    type: raster\n    path: \"imports/layers/amap_raster_holdridge_nc.tif\"\n    description: \"Holdridge\"\n```\n\n### transform.yml\n```yaml\n##################################################################\n# 1) CONFIG POUR LES TAXONS\n##################################################################\n- group_by: taxon\n  identifier: id_taxonref\n  source_table_name: occurrences\n  source_location_field: geo_pt\n  reference_table_name: taxon_ref\n  \n  widgets_data:\n    general_info:\n      # On veut un champ JSON \"general_info\" qui combine plusieurs informations \n      # (ex: nom du taxon, rang, nombre d'occurrences\u2026)\n      transformations:\n        - name: collect_fields\n          items:\n            - source: taxonomy\n              field: full_name\n              key: \"name\"\n            - source: taxonomy\n              field: rank_name\n              key: \"rank\"\n            - source: occurrences\n              transformation: count\n              key: \"occurrences_count\"\n\n    distribution_map:\n      # Doit contenir les coordonn\u00e9es des occurrences, pr\u00eates \u00e0 \u00eatre affich\u00e9es\n      # Sous forme d'un GeoJSON ou d'un array de points.\n      transformations:\n        - name: coordinates\n          source: occurrences\n          source_field: geo_pt\n\n    top_species:\n      transformations:\n        - name: top\n          source: occurrences\n          target_ranks: [\"esp\u00e8ce\", \"sous-esp\u00e8ce\"]\n          count: 10\n          # ex: { \"labels\": [...], \"values\": [...], ... }\n\n    distribution_substrat:\n      # \"Ultramafique vs Non\u2010UM\"\n      transformations:\n        - name: count_bool\n          source_field: in_um\n          # ex: { \"um\": 30, \"num\": 50, ... }\n\n    phenology_distribution:\n      # Fleurs / Fruits par mois\n      transformations:\n        - name: temporal_phenology\n          source_fields:\n            fleur: flower\n            fruit: fruit\n          time_field: month_obs\n          # ex: structure un objet : { \"month_data\": [...], \"colors\": {...} }\n\n    dbh_distribution:\n      # Distribution diam\u00e9trique + histogramme\n      transformations:\n        - name: dbh_bins\n          source_field: dbh\n          bins: [10, 20, 30, 40, 50, 75, 100, 200, 300, 400, 500]\n          # ex: { \"bins\": [...], \"counts\": [...] }\n\n    dbh:\n      # Diam\u00e8tre max (exemple)\n      transformations:\n        - name: max_value\n          source_field: dbh\n          units: \"cm\"\n          max_value: 500\n          # ex: { \"value\": 120, \"max\": 500, \"units\": \"cm\" }\n\n    height_max:\n      # Hauteur max\n      transformations:\n        - name: max_value\n          source_field: height\n          units: \"m\"\n          max_value: 40\n          # ex: { \"value\": 35, \"max\": 40, \"units\": \"m\" }\n\n    elevation_distribution:\n      # Bins altitudinaux\n      transformations:\n        - name: histogram\n          source_field: elevation\n          bins: [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1700]\n          # ex: { \"bins\": [...], \"counts\": [...] }\n\n    rainfall_distribution:\n      transformations:\n        - name: histogram\n          source_field: rainfall\n          bins: [1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000]\n\n    holdridge_distribution:\n      transformations:\n        - name: histogram\n          source_field: holdridge\n          bins: [1, 2, 3]\n          labels: [\"Sec\", \"Humide\", \"Tr\u00e8s humide\"]\n\n    strata_distribution:\n      transformations:\n        - name: histogram\n          source_field: strata\n          bins: [1, 2, 3, 4]\n          labels: [\"Sous-bois\", \"Sous-Canop\u00e9e\", \"Canop\u00e9e\", \"Emergent\"]\n\n    wood_density:\n      # On veut la densit\u00e9 moyenne & extr\u00eames par ex.\n      transformations:\n        - name: stats_min_mean_max\n          source_field: wood_density\n          units: \"g/cm3\"\n          max_value: 1.2\n          # ex: { \"mean\": 0.65, \"max\": 1.2, \"min\": 0.4 }\n\n    bark_thickness:\n      transformations:\n        - name: stats_min_mean_max\n          source_field: bark_thickness\n          units: \"mm\"\n          max_value: 80\n\n    leaf_sla:\n      transformations:\n        - name: stats_min_mean_max\n          source_field: leaf_sla\n          units: \"g/m2\"\n          max_value: 50\n\n    leaf_area:\n      transformations:\n        - name: stats_min_mean_max\n          source_field: leaf_area\n          units: \"cm2\"\n          max_value: 1500\n\n    leaf_thickness:\n      transformations:\n        - name: stats_min_mean_max\n          source_field: leaf_thickness\n          units: \"\u00b5m\"\n          max_value: 800\n##################################################################\n# 2) CONFIG POUR LES PLOTS\n##################################################################\n- group_by: plot\n  identifier: id_source\n  source_table_name: occurrences\n  reference_table_name: plot_ref\n  pivot_table_name: occurrences_plots\n  filter: \n    field: source\n    value: occ_ncpippn\n\n  widgets_data:\n\n    general_info:\n      transformations:\n        - name: collect_fields\n          items:\n            - source: plot_ref\n              field: locality \n              key: \"name\"\n            - source: plots\n              field: elevation\n              key: \"elevation\"\n              units: \"m\"\n            - source: plots\n              field: rainfall\n              key: \"rainfall\"\n              units: \"mm/an\"\n            - source: plots\n              field: holdridge\n              key: \"holdridge\"\n              labels: [\"Sec\", \"Humide\", \"Tr\u00e8s humide\"]\n            - source: plots\n              field: substrat\n              key: \"substrat\"\n              labels: \n                UM: \"Substrat ultramafique\"\n                VS: \"Substrat non ultramafique\"\n            - source: plots\n              field: nb_families\n              key: \"nb_families\"\n            - source: plots\n              field: nb_species\n              key: \"nb_species\"\n            - source: occurrences\n              transformation: count\n              key: \"occurrences_count\"\n\n    map_panel:\n      # Localisation de la parcelle\n      transformations:\n        - name: get_geometry\n          source: plots\n          source_field: geometry\n        # ex: un JSON \"map\" : { \"coordinates\": [...], \"label\": ... }\n\n    top_families:\n      transformations:\n        - name: top\n          source: occurrences\n          target_ranks: [\"famille\"]\n          count: 10\n        # ex: { \"labels\": [...], \"values\": [...] }\n\n    top_species:\n      transformations:\n        - name: top\n          source: occurrences\n          target_ranks: [\"esp\u00e8ce\", \"sous-esp\u00e8ce\"]\n          count: 10\n        # ex: { \"labels\": [...], \"values\": [...] }\n        \n\n    dbh_distribution:\n      transformations:\n        - name: dbh_bins\n          source_field: dbh\n          bins: [10, 20, 30, 40, 50, 75, 100, 200, 300, 400, 500]\n        # ex: { \"bins\": [...], \"counts\": [...], ... }\n\n    strata_distribution:\n      transformations:\n        - name: histogram\n          source_field: strata\n          bins: [1, 2, 3, 4]\n          labels: [\"Sous-Bois\", \"Sous-Canop\u00e9e\", \"Canop\u00e9e\", \"Emergent\"]\n        # ex: { \"bins\": [...], \"counts\": [...], ... }\n\n    height:\n      transformations:\n        - name: mean_value\n          source_field: height\n          units: \"m\"\n          max_value: 40\n        # ex: { \"value\": 25, \"max\": 40, \"units\": \"m\" }\n\n    wood_density:\n      transformations:\n        - name: mean_value\n          source_field: wood_density\n          units: \"g/cm3\"\n          max_value: 1.2\n        # ex: { \"value\": 0.65, \"max\": 1.2, \"units\": \"g/cm3\" }\n\n    basal_area:\n      transformations:\n        - name: identity_value\n          source: plots\n          source_field: basal_area\n          units: \"m\u00b2/ha\"\n          max_value: 100\n        # ex: { \"value\": 25, \"max\": 100, \"units\": \"m\u00b2/ha\" }\n\n    richness:\n      transformations:\n        - name: identity_value\n          source: plots\n          source_field: nb_species\n          max_value: 50\n        # ex: { \"value\": 25, \"max\": 50, \"units\": \"\" }\n\n    shannon:\n      transformations:\n        - name: identity_value\n          source: plots\n          source_field: shannon\n          max_value: 5\n        # ex: { \"value\": 2.5, \"max\": 5, \"units\": \"\" }\n\n    pielou:\n      transformations:\n        - name: identity_value\n          source: plots\n          source_field: pielou\n          max_value: 1\n        # ex: { \"value\": 0.5, \"max\": 1, \"units\": \"\" }\n\n    simpson:\n      transformations:\n        - name: identity_value\n          source: plots\n          source_field: simpson\n          max_value: 1\n        # =>ex:{ \"value\": 0.5, \"max\": 1, \"units\": \"\" }\n        \n\n    biomass:\n      transformations:\n        - name: identity_value\n          source: plots\n          source_field: biomass\n          units: \"t/ha\"\n          max_value: 800\n##################################################################\n# 3) CONFIG POUR LES SHAPES\n##################################################################\n- group_by: shape\n  occurrence_location_field: geo_pt\n  raw_data: imports/row_shape_stats.csv\n  identifier: \"id\"\n\n  widgets_data:\n\n    # 1) CHAMP \"general_info\"\n    #    On assemble plusieurs champs (surface_totale, forest_area, pluviometrie, altitude, etc.)\n    general_info:\n      transformations:\n        - name: collect_fields\n          # \u00c0 impl\u00e9menter de mani\u00e8re g\u00e9n\u00e9rique.\n          # On r\u00e9cup\u00e8re dans row_shape_stats plusieurs class_object (land_area_ha, forest_area_ha, etc.)\n          items:\n            - source: shape_ref\n              field: label\n              key: \"name\"\n            - source: raw_data\n              class_object: \"land_area_ha\"\n              key: \"land_area_ha\"\n              units: \"ha\"\n              format: \"number\"     \n            - source: raw_data\n              class_object: \"forest_area_ha\"\n              key: \"forest_area_ha\"\n              units: \"ha\"\n              format: \"number\"\n            - source: raw_data\n              class_object: \"forest_mining_ha\"\n              key: \"forest_mining_ha\"\n              units: \"ha\"\n              format: \"number\"\n            - source: raw_data\n              class_object: \"forest_reserve_ha\"\n              key: \"forest_reserve_ha\"\n              units: \"ha\"\n              format: \"number\"\n            - source: raw_data\n              class_object: \"forest_ppe_ha\"\n              key: \"forest_ppe_ha\"\n              units: \"ha\"\n              format: \"number\"\n            - source: raw_data\n              class_object: [\"rainfall_min\", \"rainfall_max\"]\n              key: \"rainfall\"\n              units: \"mm/an\"\n              format: \"range\"\n            - source: raw_data\n              class_object: \"elevation_median\"\n              key: \"elevation_median\"\n              units: \"m\"\n              format: \"number\"\n            - source: raw_data\n              class_object: \"elevation_max\"\n              key: \"elevation_max\"\n              units: \"m\"\n              format: \"number\"\n              # => ex. {\"label\": \"PROVINCE NORD\", \"land_area_ha\": 941252.41325591, \"forest_area_ha\": 321711.765827868, \"forest_mining_ha\": 21106, \"forest_reserve_ha\": 11800, \"forest_ppe_ha\": 48275, \"rainfall\": {\"min\": 510, \"max\": 4820}, \"elevation_median\": 214, \"elevation_max\": 1622}\n\n    # 2) CHAMP \"geography\"\n    #    On peut inclure la g\u00e9om\u00e9trie du shape + la g\u00e9om\u00e9trie \"forest cover\" si besoin\n    geography:\n      transformations:\n        - name: geometry_coords\n          # Filtrer le shape_gdf pour en extraire shape_coords\n          # Filtrer forest_gdf pour en extraire forest_coords\n          # Renvoyer un JSON { \"shape_coords\": ..., \"forest_coords\": ... }\n\n    # 3) CHAMP \"forest_cover\"\n    #    On veut faire un \"pie\" ou un \"doughnut\" => typiquement, on va chercher\n    #    class_object = \"cover_forest\", \"cover_forestum\", \"cover_forestnum\"\n    forest_cover:\n      transformations:\n        - name: extract_multi_class_object\n          # Dans row_shape_stats, on a:\n          #   cover_forest     => For\u00eat / Hors-For\u00eat\n          #   cover_forestum   => For\u00eat / Hors-For\u00eat\n          #   cover_forestnum  => For\u00eat / Hors-For\u00eat\n          # L\u2019id\u00e9e est de regrouper ces 3 distributions dans un unique JSON, ex.:\n          # {\n          #   \"emprise\": { \"for\u00eat\": 0.34, \"hors_foret\": 0.66 },\n          #   \"um\": { \"for\u00eat\": 0.23, \"hors_foret\": 0.77 },\n          #   \"num\": { \"for\u00eat\": 0.37, \"hors_foret\": 0.63 }\n          # }\n          params:\n            # On peut param\u00e9trer lister les \"class_object\" qu'on veut extraire\n            - label: \"emprise\"\n              class_object: \"cover_forest\"\n            - label: \"um\"\n              class_object: \"cover_forestum\"\n            - label: \"num\"\n              class_object: \"cover_forestnum\"\n          # Ton code it\u00e8re sur param et lit row_shape_stats[(class_object == ...)] \n          # pour \"For\u00eat\" et \"Hors-for\u00eat\".\n\n    # 4) CHAMP \"land_use\"\n    land_use:\n      transformations:\n        - name: extract_by_class_object\n          class_object: \"land_use\"\n          categories_order: \n            - \"NUM\"\n            - \"UM\"\n            - \"Sec\"\n            - \"Humide\"\n            - \"Tr\u00e8s Humide\"\n            - \"R\u00e9serve\"\n            - \"PPE\"\n            - \"Concessions\"\n            - \"For\u00eat\"\n          # => parse la table row_shape_stats pour class_object = \"land_use\"\n          # => {\"categories\": [\"NUM\", \"UM\", \"Sec\", \"Humide\", \"Tr\\u00e8s Humide\", \"R\\u00e9serve\", \"PPE\", \"Concessions\", \"For\\u00eat\"], \"values\": [720516.368203804, 220736.045052106, 245865.633017676, 564601.884540423, 130784.895697811, 14272.8699792226, 94334.7084348428, 121703.503624097, 321711.765827868]}\n\n    # 5) CHAMP \"elevation_distribution\"\n    elevation_distribution:\n      transformations:\n        - name: extract_elevation_distribution\n          # Dedans, tu peux soit r\u00e9utiliser la fonction \"calculate_elevation_distribution\",\n          # soit lire row_shape_stats \"forest_elevation\" / \"land_elevation\" / ...\n          # L'id\u00e9e : renvoyer un JSON type:\n          # {\n          #   \"altitudes\": [100,200,...,1700],\n          #   \"forest\": [...],\n          #   \"non_forest\": [...]\n          # }\n\n    # 6) CHAMP \"holdridge\"\n    holdridge:\n      transformations:\n        - name: extract_holdridge\n          # Ce param mappera holdridge_forest (Sec/Humide/Tr\u00e8s Humide)\n          # => ex. {\"forest\": {\"sec\": 0.0222477792523791, \"humide\": 0.2235085776628972, \"tres_humide\": 0.0960348154600766}, \"non_forest\": {\"sec\": 0.2389633789397109, \"humide\": 0.3763325240320183, \"tres_humide\": 0.0429129246529177}}\n\n    # 7) CHAMP \"forest_types\"\n    forest_types:\n      transformations:\n        - name: extract_by_class_object\n          class_object: \"cover_foresttype\"\n          # => ex. { \"For\u00eat coeur\": 0.064..., \"For\u00eat mature\": 0.51..., \"For\u00eat secondaire\": 0.42... }\n\n    # 8) CHAMP \"forest_cover_by_elevation\"\n    forest_cover_by_elevation:\n      transformations:\n        - name: extract_elevation_matrix\n          # On peut imaginer qu\u2019on lit forest_elevation, forest_um_elevation, forest_num_elevation,\n          # => ex. { \"altitudes\": [...], \"um\": [...], \"num\": [...], \"hors_foret_um\": [...], \"hors_foret_num\": [...] }\n\n    # 9) CHAMP \"forest_types_by_elevation\"\n    forest_types_by_elevation:\n      transformations:\n        - name: extract_forest_types_by_elevation\n          # Va lire:\n          #   forest_secondary_elevation\n          #   forest_mature_elevation\n          #   forest_core_elevation\n          # pour assembler un JSON:\n          # {\n          #   \"altitudes\": [...],\n          #   \"secondaire\": [...],\n          #   \"mature\": [...],\n          #   \"coeur\": [...]\n          # }\n\n    # 10) CHAMP \"fragmentation\"\n    fragmentation:\n      transformations:\n        - name: collect_fields\n          # On veut juste lire 'fragment_meff_cbc' => ex. 189.98456266\n          items:\n            - source: raw_data\n              class_object: fragment_meff_cbc\n              key: \"meff\"\n\n    # 11) CHAMP \"fragmentation_distribution\"\n    fragmentation_distribution:\n      transformations:\n        - name: extract_distribution\n          class_object: \"forest_fragmentation\"\n          # => Sur row_shape_stats, on a \"forest_fragmentation\" + class_name = [10, 20, 30...], class_value = ...\n          # => Produire { \"sizes\": [...], \"values\": [...] }\n```\n\n### export.yml\n```yaml\n##################################################################\n# 1) PR\u00c9SENTATION POUR LES TAXONS\n##################################################################\n- group_by: taxon\n  widgets:\n    general_info:\n      type: info_panel\n      title: \"Informations g\u00e9n\u00e9rales\"\n      layout: grid\n      fields:\n        - source: rank\n          label: \"Rang\"\n        - source: occurrences_count\n          label: \"Nombre d'occurrences\"\n          format: \"number\"\n      # => Ton JSON \"general_info\" pourrait contenir { \"taxon_name\": \"...\", \"occurrences_count\": 123, ... }\n\n    distribution_map:\n      type: map_panel\n      title: \"Distribution g\u00e9ographique\"\n      description: Distribution g\u00e9ographique des occurrences du taxon et de ses sous-taxons\n      source: distribution_map\n      layout: full_width\n      layers:\n        - id: \"occurrences\"\n          source: coordinates\n          style:\n            color: \"#1fb99d\"\n            weight: 1\n            fillColor: \"#00716b\"\n            fillOpacity: 0.5\n            radius: 2000\n      # => JSON { \"coordinates\": [...], \"style\": {...} } (selon le format produit)\n\n    top_species:\n      type: bar_chart\n      title: \"Sous-taxons principaux\"\n      description: \"Principaux sous-taxons (esp\u00e8ce, sous-esp\u00e8ce)\"\n      source: top_species\n      sortData: true\n      datasets:\n        - label: 'Occurrences'\n          data_key: counts\n          generateColors: true \n      labels_key: tops\n      options:\n        indexAxis: 'y'\n        scales:\n          x:\n            beginAtZero: true\n            grid: {\n              display: true,\n              drawBorder: true,\n              drawOnChartArea: true,\n              drawTicks: true\n            }\n            ticks: {\n              stepSize: 5\n            }\n            title:\n              display: true\n              text: \"Nombre d'occurrences\"\n          y:\n            grid: {\n              display: false\n            }\n        plugins:\n          legend: {\n            display: false\n          }\n        maintainAspectRatio: false\n        responsive: true\n      # => ex. JSON { \"tops\": [...], \"counts\": [...] }\n\n    dbh_distribution:\n      type: bar_chart\n      title: \"Distribution diam\u00e9trique (DBH)\"\n      description: R\u00e9partition des occurrences par classe de diam\u00e8tre\n      source: dbh_distribution\n      datasets:\n        - label: \"Occurrences\"\n          data_key: \"counts\"\n          backgroundColor: \"#4CAF50\"\n      labels_key: \"bins\"\n      options:\n        scales:\n          y:\n            title:\n              display: true\n              text: \"Nombre d'occurrences\"\n          x:\n            title:\n              display: true\n              text: \"DBH (cm)\"\n\n    phenology_distribution:\n      type: line_chart   \n      title: \"Ph\u00e9nologie\"\n      description: Ph\u00e9nologie des \u00e9tats fertiles (fleur, fruit) du taxon et de ses sous-taxons\n      source: phenology_distribution\n      datasets:\n        - label: \"Fleur\"\n          data_key: \"month_data.fleur\"\n          borderColor: \"#FF9800\"    \n          backgroundColor: \"transparent\"\n          tension: 0.4\n          pointRadius: 4\n        - label: \"Fruit\"\n          data_key: \"month_data.fruit\"\n          borderColor: \"#4CAF50\"    \n          backgroundColor: \"transparent\"\n          tension: 0.4\n          pointRadius: 4\n      labels: [\"Jan\", \"Fev\", \"Mar\", \"Apr\", \"Mai\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\n      options:\n        scales:\n          y:\n            title:\n              display: true\n              text: \"Fr\u00e9quence (%)\"\n\n    distribution_substrat:\n      type: doughnut_chart\n      title: \"Distribution substrat\"\n      description: \"Distribution des occurrences par substrat (= fr\u00e9quence du taxon par substrat)\"\n      source: distribution_substrat\n      datasets:\n        - label: 'Distribution substrat'\n          data_keys: ['um', 'num']\n          backgroundColor: ['#b08d57', '#78909c']\n          borderColor: '#ffffff'\n          borderWidth: 2\n      labels: ['Ultramafique (UM)', 'non-Ultramafique (NUM)']\n      options:\n        cutout: '1%'\n        plugins:\n          legend:\n            display: true\n            position: 'top'\n            align: 'center'\n            labels:\n              usePointStyle: false\n              padding: 20\n              boxWidth: 30\n              color: '#666666'\n              font:\n                size: 12\n        layout:\n          padding:\n            top: 20\n\n    holdridge_distribution:\n      type: bar_chart\n      title: \"Milieu de vie\"\n      description: Fr\u00e9quence des occurrences du taxon et de ses sous-taxons par milieu de vie\n      source: holdridge_distribution\n      datasets:\n        - label: \"Occurrences\"\n          data_key: \"counts\"\n          backgroundColor: [\"#8B0000\", \"#FFEB3B\", \"#1E88E5\"]  # Rouge, Jaune, Bleu\n      labels_key: \"labels\"\n      options:\n        scales:\n          y:\n            title:\n              display: true\n              text: \"Altitude (m)\"\n\n    rainfall_distribution:\n      type: bar_chart\n      title: \"R\u00e9partition pluviom\u00e9trie\"\n      description: Distribution pluviom\u00e9trique des occurrences du taxon (= fr\u00e9quence par classe de pluviom\u00e9trie)\n      source: rainfall_distribution\n      datasets:\n        - label: \"Occurrences\"\n          data_key: \"counts\"\n          backgroundColor: \"#2196F3\"    # Bleu comme dans l'image\n      labels_key: \"bins\"\n      options:\n        indexAxis: \"y\"\n        scales:\n          x:\n            title:\n              display: true\n              text: \"Occurrences\"\n          y:\n            title:\n              display: true\n              text: \"Pluviom\u00e9trie (mm/an)\"\n\n    strata_distribution:\n      type: bar_chart\n      title: \"Stratification\"\n      description: R\u00e9partition des occurrences par strate\n      source: strata_distribution\n      datasets:\n        - label: \"Occurrences\"\n          data_key: \"counts\"\n          backgroundColor: [\"#90A4AE\", \"#66BB6A\", \"#43A047\", \"#2E7D32\"]  # Du plus clair au plus fonc\u00e9\n          borderWidth: 1\n      labels_key: \"labels\"\n      options:\n        indexAxis: 'y'\n        scales:\n          x:\n            title:\n              display: true\n              text: \"Nombre d'occurrences\"\n\n    height_max:\n      type: gauge\n      title: \"Hauteur maximale\"\n      description: Hauteur maximale atteint par le taxon et ses sous-taxons\n      source: height_max\n      value_key: \"value\"\n      options:\n        min: 0\n        max: 40\n        units: \"m\"\n        sectors:\n          - color: '#f02828'\n            range: [0, 10]\n          - color: '#fe6a00'\n            range: [10, 18]\n          - color: '#e8dd0f'\n            range: [18, 25]\n          - color: '#81e042'\n            range: [25, 33]\n          - color: '#049f50'\n            range: [33, 40]\n\n    wood_density:\n      type: gauge\n      title: \"Densit\u00e9 de bois\"\n      description: Densit\u00e9 de bois moyenne mesur\u00e9 avec une Tari\u00e8re de Pressler\n      source: wood_density\n      value_key: \"mean\"\n      options:\n        min: 0\n        max: 1.2\n        units: \"g/cm\u00b3\"\n        sectors:\n          - color: '#f02828'\n            range: [0.000, 0.240]\n          - color: '#fe6a00'\n            range: [0.240, 0.480]\n          - color: '#e8dd0f'\n            range: [0.480, 0.720]\n          - color: '#81e042'\n            range: [0.720, 0.960]\n          - color: '#049f50'\n            range: [0.960, 1.200]\n\n    bark_thickness:\n      type: gauge\n      title: \"\u00c9paisseur d'\u00e9corce\"\n      description: Epaisseur moyenne de l'\u00e9corce mesur\u00e9e \u00e0 la jauge \u00e0 \u00e9corce\n      source: bark_thickness\n      value_key: \"mean\"\n      options:\n        min: 0\n        max: 80\n        units: \"mm\"\n        sectors:\n          - color: '#f02828'\n            range: [0, 16]\n          - color: '#fe6a00'\n            range: [16, 32]\n          - color: '#e8dd0f'\n            range: [32, 48]\n          - color: '#81e042'\n            range: [48, 64]\n          - color: '#049f50'\n            range: [64, 80]\n\n    leaf_sla:\n      type: gauge\n      title: \"Surface foliaire sp\u00e9cifique\"\n      description: Surface foliaire sp\u00e9cifique du taxon et de ses sous-taxons\n      source: leaf_sla\n      value_key: \"mean\"\n      options:\n        min: 0\n        max: 50\n        units: \"m\u00b2\u00b7kg\u207b\u00b9\"\n        sectors:\n          - color: '#f02828'\n            range: [0, 10]\n          - color: '#fe6a00'\n            range: [10, 20]\n          - color: '#e8dd0f'\n            range: [20, 30]\n          - color: '#81e042'\n            range: [30, 40]\n          - color: '#049f50'\n            range: [40, 50]\n\n    leaf_area:\n      type: gauge\n      title: \"Surface foliaire\"\n      description: Surface foliaire du taxon et de ses sous-taxons\n      source: leaf_area\n      value_key: \"mean\"\n      options:\n        min: 0\n        max: 1500\n        units: \"cm\u00b2\"\n        sectors:\n          - color: '#f02828'\n            range: [0, 300]\n          - color: '#fe6a00'\n            range: [300, 600]\n          - color: '#e8dd0f'\n            range: [600, 900]\n          - color: '#81e042'\n            range: [900, 1200]\n          - color: '#049f50'\n            range: [1200, 1500]\n\n    leaf_thickness:\n      type: gauge\n      title: \"\u00c9paisseur des feuilles\"\n      description: Epaisseur moyenne des feuilles du taxon et de ses sous-taxons\n      source: leaf_thickness\n      value_key: \"mean\"\n      options:\n        min: 0\n        max: 800\n        units: \"\u00b5m\"\n        sectors:\n          - color: '#f02828'\n            range: [0, 160]\n          - color: '#fe6a00'\n            range: [160, 320]\n          - color: '#e8dd0f'\n            range: [320, 480]\n          - color: '#81e042'\n            range: [480, 640]\n          - color: '#049f50'\n            range: [640, 800]\n##################################################################\n# 2) PR\u00c9SENTATION POUR LES PLOTS\n##################################################################\n- group_by: plot\n  widgets:\n    general_info:\n      type: info_panel\n      title: \"Informations g\u00e9n\u00e9rales\"\n      layout: grid\n      fields:\n        - source: elevation\n          label: \"altitudes\"\n        - source: rainfall\n          label: \"Pr\u00e9cipitation annuelle moyenne\"\n        - source: holdridge\n          label: \"Milieu de vie\"\n        - source: substrat\n          label: \"Substrat\"\n        - source: nb_families\n          label: \"Nombre de familles\"\n        - source: nb_species\n          label: \"Nombre d'esp\u00e8ces\"\n        - source: occurrences_count\n          label: \"Nombre d'occurrences\"\n          format: \"number\"\n\n      # => ex. { \"plot_name\": \"Parcelle A\", \"basal_area\": 25.3, \"trees_count\": 364, ... }\n\n    map_panel:\n      type: map_panel\n      title: \"Localisation de la parcelle\"\n      source: map_panel\n      layout: full_width\n      layers:\n        - id: \"plot\"\n          source: geometry\n          style:\n            color: \"#1fb99d\"\n            weight: 2\n            fillOpacity: 0\n      # => ex. JSON { \"coordinates\": [...], \"style\": {...} }\n\n    top_families:\n      type: bar_chart\n      title: \"Familles dominantes\"\n      description: \"Les dix familles botaniques les plus fr\u00e9quentes de la parcelle\"\n      source: top_families\n      sortData: true\n      datasets:\n        - label: 'Occurrences'\n          data_key: counts\n          generateColors: true \n      labels_key: tops\n      options:\n        indexAxis: 'y'\n        scales:\n          x:\n            beginAtZero: true\n            grid: {\n              display: true,\n              drawBorder: true,\n              drawOnChartArea: true,\n              drawTicks: true\n            }\n            ticks: {\n              stepSize: 5\n            }\n            title:\n              display: true\n              text: \"Nombre d'occurrences\"\n          y:\n            grid: {\n              display: false\n            }\n        plugins:\n          legend: {\n            display: false\n          }\n        maintainAspectRatio: false\n        responsive: true\n      # => ex. JSON { \"labels\": [...], \"values\": [...], ... }\n\n    top_species:\n      type: bar_chart\n      title: \"Sous-taxons principaux\"\n      description: \"Les dix esp\u00e8ces botaniques les plus fr\u00e9quentes de la parcelle\"\n      source: top_species\n      sortData: true\n      datasets:\n        - label: 'Occurrences'\n          data_key: counts\n          generateColors: true \n      labels_key: tops\n      options:\n        indexAxis: 'y'\n        scales:\n          x:\n            beginAtZero: true\n            grid: {\n              display: true,\n              drawBorder: true,\n              drawOnChartArea: true,\n              drawTicks: true\n            }\n            ticks: {\n              stepSize: 5\n            }\n            title:\n              display: true\n              text: \"Nombre d'occurrences\"\n          y:\n            grid: {\n              display: false\n            }\n        plugins:\n          legend: {\n            display: false\n          }\n        maintainAspectRatio: false\n        responsive: true\n      # => ex. JSON { \"tops\": [...], \"counts\": [...] }\n\n    dbh_distribution:\n      type: bar_chart\n      title: \"Distribution diam\u00e9trique (DBH)\"\n      description: R\u00e9partition des occurrences par classe de diam\u00e8tre\n      source: dbh_distribution\n      datasets:\n        - label: \"Occurrences\"\n          data_key: \"counts\"\n          backgroundColor: \"#4CAF50\"\n      labels_key: \"bins\"\n      options:\n        scales:\n          y:\n            title:\n              display: true\n              text: \"Nombre d'occurrences\"\n          x:\n            title:\n              display: true\n              text: \"DBH (cm)\"\n\n    strata_distribution:\n      type: bar_chart\n      title: \"Stratification\"\n      description: R\u00e9partition des occurrences par strate\n      source: strata_distribution\n      datasets:\n        - label: \"Occurrences\"\n          data_key: \"counts\"\n          backgroundColor: [\"#90A4AE\", \"#66BB6A\", \"#43A047\", \"#2E7D32\"]  # Du plus clair au plus fonc\u00e9\n          borderWidth: 1\n      labels_key: \"labels\"\n      options:\n        indexAxis: 'y'\n        scales:\n          x:\n            title:\n              display: true\n              text: \"Nombre d'occurrences\"\n\n    height:\n      type: gauge\n      title: \"Hauteur moyenne\"\n      source: height\n      value_key: \"value\"\n      options:\n        min: 0\n        max: 40\n        units: \"m\"\n        sectors:\n          - color: '#f02828'\n            range: [0, 5]\n          - color: '#fe6a00'\n            range: [5, 10]\n          - color: '#e8dd0f'\n            range: [10, 15]\n          - color: '#81e042'\n            range: [15, 20]\n          - color: '#049f50'\n            range: [20, 25]\n\n    wood_density:\n      type: gauge\n      title: \"Densit\u00e9 de bois\"\n      description: Densit\u00e9 de bois moyenne mesur\u00e9 avec une Tari\u00e8re de Pressler\n      source: wood_density\n      value_key: \"value\"\n      options:\n        min: 0\n        max: 1.2\n        units: \"g/cm\u00b3\"\n        sectors:\n          - color: '#f02828'\n            range: [0.000, 0.240]\n          - color: '#fe6a00'\n            range: [0.240, 0.480]\n          - color: '#e8dd0f'\n            range: [0.480, 0.720]\n          - color: '#81e042'\n            range: [0.720, 0.960]\n          - color: '#049f50'\n            range: [0.960, 1.200]\n\n    basal_area:\n      type: gauge\n      title: \"Aire basale\"\n      source: basal_area\n      value_key: \"value\"\n      options:\n        min: 0\n        max: 100\n        units: \"m\u00b2/ha\"\n        sectors:\n          - color: '#f02828'\n            range: [0, 15]\n          - color: '#fe6a00'\n            range: [15, 30]\n          - color: '#e8dd0f'\n            range: [30, 45]\n          - color: '#81e042'\n            range: [45, 60]\n          - color: '#049f50'\n            range: [60, 76]\n\n    richness:\n      type: gauge\n      title: \"Richesse\"\n      source: richness\n      value_key: \"value\"\n      options:\n        min: 0\n        max: 130\n        units: \"\"\n        sectors:\n          - color: '#f02828'\n            range: [0, 28]\n          - color: '#fe6a00'\n            range: [28, 52]\n          - color: '#e8dd0f'\n            range: [52, 78]\n          - color: '#81e042'\n            range: [78, 104]\n          - color: '#049f50'\n            range: [104, 130]\n    shannon:\n      type: gauge\n      title: \"Shannon\"\n      source: shannon\n      value_key: \"value\"\n      options:\n        min: 0\n        max: 5\n        units: \"\"\n        sectors:\n          - color: '#f02828'\n            range: [0.0, 1.0]\n          - color: '#fe6a00'\n            range: [1.0, 2.0]\n          - color: '#e8dd0f'\n            range: [2.0, 3.0]\n          - color: '#81e042'\n            range: [3.0, 4.0]\n          - color: '#049f50'\n            range: [4.0, 5.0]\n\n    pielou:\n      type: gauge\n      title: \"Pielou\"\n      source: pielou\n      value_key: \"value\"\n      options:\n        min: 0\n        max: 1\n        units: \"\"\n        sectors:\n          - color: '#f02828'\n            range: [0.00, 0.20]\n          - color: '#fe6a00'\n            range: [0.20, 0.40]\n          - color: '#e8dd0f'\n            range: [0.40, 0.60]\n          - color: '#81e042'\n            range: [0.60, 0.80]\n          - color: '#049f50'\n            range: [0.80, 1.0]\n\n    simpson:\n      type: gauge\n      title: \"Simpson\"\n      source: simpson\n      value_key: \"value\"\n      options:\n        min: 0\n        max: 1\n        units: \"\"\n        sectors:\n          - color: '#f02828'\n            range: [0.00, 0.20]\n          - color: '#fe6a00'\n            range: [0.20, 0.40]\n          - color: '#e8dd0f'\n            range: [0.40, 0.60]\n          - color: '#81e042'\n            range: [0.60, 0.80]\n          - color: '#049f50'\n            range: [0.80, 1.0]\n\n    biomass:\n      type: gauge\n      title: \"Biomasse\"\n      source: biomass\n      value_key: \"value\"\n      options:\n        min: 0\n        max: 800\n        units: \"t/ha\"\n        sectors:\n          - color: '#f02828'\n            range: [0, 160]\n          - color: '#fe6a00'\n            range: [160, 320]\n          - color: '#e8dd0f'\n            range: [320, 480]\n          - color: '#81e042'\n            range: [480, 640]\n          - color: '#049f50'\n            range: [640, 800]\n      \n##################################################################\n# 3) PR\u00c9SENTATION POUR LES SHAPES\n##################################################################\n- group_by: shape\n  widgets:\n    general_info:\n      type: info_panel\n      title: \"Informations g\u00e9n\u00e9rales\"\n      layout: grid\n      fields:\n        - source: land_area_ha\n          label: \"Surface totale\"\n          suffix: \" ha\"\n          format: \"number\"\n        - source: forest_area_ha\n          label: \"Surface for\u00eat\"\n          suffix: \" ha\"\n          format: \"number\"\n        - source: forest_mining_ha\n          label: \"For\u00eat sur mine\"\n          suffix: \" ha\"\n          format: \"number\"\n        - source: forest_reserve_ha\n          label: \"For\u00eat en r\u00e9serve\"\n          suffix: \" ha\"\n          format: \"number\"\n        - source: forest_ppe_ha\n          label: \"For\u00eat sur captage (PPE)\"\n          suffix: \" ha\"\n          format: \"number\"\n        - source: rainfall\n          label: \"Pluviom\u00e9trie\"\n          format: \"range\"\n          suffix: \" mm/an\"\n        - source: elevation_median\n          label: \"Altitude m\u00e9diane\"\n          format: \"number\"\n          suffix: \" m\"\n        - source: elevation_max\n          label: \"Altitude maximale\"\n          format: \"number\"\n          suffix: \" m\"\n\n    map_panel:\n      type: map_panel\n      title: \"Distribution de la for\u00eat\"\n      description: \"Distribution de la for\u00eat dans l'emprise s\u00e9lectionn\u00e9e\"\n      source: geography\n      layers:\n        - id: shape\n          source: geography.shape_coords\n          style:\n            color: \"#1fb99d\"\n            weight: 2\n            fillOpacity: 0\n        - id: forest\n          source: geography.forest_coords\n          style:\n            color: \"#228b22\"\n            weight: 0.3\n            fillColor: \"#228b22cc\"\n            fillOpacity: 0.8\n\n    forest_cover:\n      type: doughnut_chart\n      title: \"Couverture foresti\u00e8re\"\n      description: \"La couverture foresti\u00e8re (= superficie de for\u00eat / superficie disponible) est un indicateur de l'importance de la for\u00eat dans le paysage.\"\n      source: forest_cover\n      datasets:\n        - label: 'Emprise'\n          data_keys: ['emprise.foret', 'emprise.hors_foret']\n          transformData: 'toPercentage'\n          backgroundColors: ['#2E7D32', '#F4E4BC']\n          borderColor: '#ffffff'\n          borderWidth: 2\n        - label: 'NUM'\n          data_keys: ['num.foret', 'num.hors_foret']\n          transformData: 'toPercentage'\n          backgroundColors: ['#2E7D32', '#C5A98B']\n          borderWidth: 2\n          borderColor: '#ffffff'\n        - label: 'UM'\n          data_keys: ['um.foret', 'um.hors_foret']\n          transformData: 'toPercentage'\n          backgroundColors: ['#2E7D32', '#8B7355']\n          borderColor: '#ffffff'\n          borderWidth: 2\n      labels: ['For\u00eat', 'Hors-for\u00eat']\n      options:\n        cutout: '20%'\n        rotation: -90\n        plugins:\n          legend:\n            display: false\n          tooltip:\n            enabled: false\n      customPlugin: 'customLabels'\n\n    land_use:\n      type: bar_chart\n      title: \"Occupation du sol\"\n      description: \"Superficie occup\u00e9e par le substrat, les milieux de vie de Holdridge, les limites administratives et la for\u00eat dans l'emprise s\u00e9lectionn\u00e9e\"\n      source: land_use\n      datasets:\n        - label: 'Occupation du sol'\n          data_key: 'values'\n          color_mapping:\n            NUM: \"#8B4513\"\n            UM: \"#CD853F\"\n            Sec: \"#8B0000\"\n            Humide: \"#FFEB3B\"\n            \"Tr\u00e8s Humide\": \"#1E88E5\"\n            R\u00e9serve: \"#4CAF50\"\n            PPE: \"#90CAF9\"\n            Concessions: \"#E57373\"\n            For\u00eat: \"#2E7D32\"\n      labels_key: 'categories'\n      options:\n        indexAxis: 'x'\n        scales:\n          x:\n            title:\n              display: true\n              text: ''\n          y:\n            title:\n              display: true\n              text: 'Superficie (ha)'\n            ticks:\n              callback: 'formatSurfaceValue'\n\n    elevation_distribution:\n      type: bar_chart\n      title: \"Distribution altitudinale\"\n      description: \"Distribution altitudinale de la for\u00eat dans l'emprise\"\n      source: elevation_distribution\n      datasets:\n        - label: 'For\u00eat'\n          data_key: 'forest'\n          backgroundColor: '#2E7D32'\n          stack: 'Stack 0'\n        - label: 'Hors-for\u00eat'\n          data_key: 'non_forest'\n          backgroundColor: '#F4E4BC'\n          stack: 'Stack 0'\n      labels_key: 'altitudes'\n      options:\n        indexAxis: 'y'\n        scales:\n          x:\n            title:\n              display: true\n              text: 'Superficie (ha)'\n          y:\n            reverse: true\n            title:\n              display: true\n              text: 'Altitude (m)'\n\n    holdridge_distribution:\n      type: bar_chart\n      title: \"For\u00eat et milieux de vie\"\n      description: \"Distribution de la for\u00eat selon les milieux de vie de Holdridge\"\n      source: holdridge\n      datasets:\n        - label: 'For\u00eat'\n          transformData: 'toPercentage'\n          data_keys: ['forest.sec', 'forest.humide', 'forest.tres_humide']\n          backgroundColor: '#2E7D32'\n          stack: 'Stack 0'\n        - label: 'Hors-for\u00eat'\n          transformData: 'toPercentage'\n          data_keys: ['non_forest.sec', 'non_forest.humide', 'non_forest.tres_humide']\n          backgroundColor: '#F4E4BC'\n          stack: 'Stack 0'\n      labels: ['Sec', 'Humide', 'Tr\u00e8s humide']\n      options:\n        indexAxis: 'x'\n        scales:\n          x:\n            title:\n              display: true\n              text: 'Type de milieu'\n          y:\n            title:\n              display: true\n              text: 'Proportion (%)'\n\n    forest_types:\n      type: doughnut_chart\n      title: \"Types forestiers\"\n      description: \"R\u00e9partition de la for\u00eat selon les trois types de for\u00eat\"\n      source: forest_types\n      customPlugin: 'forestTypeLabelsPlugin'\n      datasets:\n        - label: 'Types de for\u00eat'\n          data_key: 'values'\n          transformData: 'toPercentage'\n          backgroundColor: ['#2E7D32', '#7CB342', '#C5E1A5']\n          borderWidth: 2\n          borderColor: '#ffffff'\n      labels_key: 'categories'  # Utilise les cat\u00e9gories du JSON comme labels\n      options:\n        cutout: '60%'\n        plugins:\n          legend:\n            position: 'bottom'\n            labels:\n              padding: 20\n              font:\n                size: 12\n              usePointStyle: false\n              boxWidth: 15\n          tooltip:\n            callbacks:\n              label: 'formatForestTypeTooltip'\n\n    forest_cover_by_elevation:\n      type: bar_chart\n      title: \"Couverture foresti\u00e8re par altitude\"\n      description: \"Distribution altitudinale de la couverture foresti\u00e8re en fonction du substrat\"\n      source: forest_cover_by_elevation\n      datasets:\n        - label: 'For\u00eat (UM)'\n          data_key: 'um'\n          backgroundColor: '#90EE90'\n          stack: 'Stack 0'\n          transformData: 'negateValues'\n        - label: 'For\u00eat (NUM)'\n          data_key: 'num'\n          backgroundColor: '#2E7D32'\n          stack: 'Stack 0'\n      labels_key: 'altitudes'\n      options:\n        responsive: true\n        maintainAspectRatio: false\n        indexAxis: 'y'\n        scales:\n          x:\n            stacked: true\n            position: 'top'\n            min: -100\n            max: 100\n            grid:\n              lineWidth: 1\n              drawTicks: false\n              borderDash: [5, 5]\n            ticks:\n              callback: 'formatAbsoluteValue'\n              stepSize: 20\n              autoSkip: false\n              maxRotation: 0\n            border:\n              display: false\n          y:\n            stacked: true\n            position: 'left'\n            reverse: true\n            grid:\n              display: true\n              lineWidth: 1\n              drawTicks: false\n              borderDash: [5, 5]\n            ticks:\n              font:\n                size: 12\n            title:\n              display: true\n              text: 'Altitude (m)'\n              font:\n                size: 12\n            border:\n              display: false\n        plugins:\n          legend:\n            position: 'bottom'\n            align: 'center'\n            labels:\n              boxWidth: 10\n              padding: 15\n          title:\n            display: true\n            text: 'Couverture (%)'\n            position: 'top'\n            align: 'center'\n          tooltip:\n            mode: 'y'\n            intersect: false\n            callbacks:\n              label: 'formatForestCoverTooltip'\n\n    forest_types_by_elevation:\n      type: line_chart\n      title: \"Types forestiers par altitude\"\n      description: \"Distribution des types de for\u00eats selon l'altitude\"\n      source: forest_types_by_elevation\n      sortBy: 'altitudes'\n      datasets:\n        - label: 'For\u00eat secondaire'\n          data_key: 'secondaire'\n          transformData: 'stackedPercentage'\n          backgroundColor: '#C5E1A5'\n          borderColor: '#C5E1A5'\n          fill: true\n          pointStyle: 'circle'\n          pointRadius: 0\n          pointHoverRadius: 5\n          pointHoverBackgroundColor: '#ffffff'\n          tension: 0.4\n          stack: 'Stack 0'\n        - label: 'For\u00eat mature'\n          data_key: 'mature'\n          transformData: 'stackedPercentage'\n          backgroundColor: '#7CB342'\n          borderColor: '#7CB342'\n          fill: true\n          pointStyle: 'circle'\n          pointRadius: 0\n          pointHoverRadius: 5\n          pointHoverBackgroundColor: '#ffffff'\n          tension: 0.4\n          stack: 'Stack 0'\n        - label: 'For\u00eat de coeur'\n          data_key: 'coeur'\n          transformData: 'stackedPercentage'\n          backgroundColor: '#2E7D32'\n          borderColor: '#2E7D32'\n          fill: true\n          pointStyle: 'circle'\n          pointRadius: 0\n          pointHoverRadius: 5\n          pointHoverBackgroundColor: '#ffffff'\n          tension: 0.4\n          stack: 'Stack 0'\n      labels_key: 'altitudes'\n      options:\n        responsive: true\n        maintainAspectRatio: false\n        scales:\n          x:\n            title:\n              display: true\n              text: 'Altitude (m)'\n              font:\n                size: 12\n            grid:\n              display: false\n            ticks:\n              maxRotation: 0\n          y:\n            stacked: true\n            grid:\n              color: '#e5e5e5'\n              borderDash: [2, 2]\n            ticks:\n              callback: 'formatPercentage'\n            title:\n              display: true\n              text: 'Fr\u00e9quence (%)'\n              font:\n                size: 12\n            min: 0\n            max: 100\n        plugins:\n          legend:\n            position: 'bottom'\n            labels:\n              padding: 20\n              usePointStyle: false\n              boxWidth: 15\n          tooltip:\n            mode: 'index'\n            intersect: false\n            callbacks:\n              label: 'formatForestTypeElevationTooltip'\n        interaction:\n          mode: 'nearest'\n          axis: 'x'\n          intersect: false\n\n    fragmentation:\n      type: gauge\n      title: \"Fragmentation\"\n      description: \"La taille effective de maillage repr\u00e9sente la probabilit\u00e9 que deux points appartiennent au m\u00eame fragment de for\u00eat\"\n      source: fragmentation\n      value_key: 'meff'\n      options:\n        min: 0\n        max: 1000\n        units: 'km\u00b2'\n        sectors:\n          - color: '#f02828'\n            range: [0, 200]\n          - color: '#fe6a00'\n            range: [200, 400]\n          - color: '#e8dd0f'\n            range: [400, 600]\n          - color: '#81e042'\n            range: [600, 800]\n          - color: '#049f50'\n            range: [800, 1000]\n\n    fragmentation_distribution:\n      type: line_chart\n      title: \"Fragments forestiers\"\n      description: \"Aire cumul\u00e9e de chaque fragment forestier class\u00e9 du plus petit au plus grand\"\n      source: fragmentation_distribution\n      sortData: true\n      sortBy: 'sizes'\n      datasets:\n        - label: 'Aire Cumul\u00e9e'\n          data_key: 'values'\n          backgroundColor: '#2E7D32'\n          borderColor: '#2E7D32'\n          fill: true\n          tension: 0.3\n          pointRadius: 0\n          borderWidth: 2\n          transformData: 'toPercentage'\n      labels_key: 'sizes'\n      options:\n        scales:\n          x:\n            type: 'logarithmic'\n            title:\n              display: true\n              text: 'Surface (ha)'\n            grid:\n              display: false\n          y:\n            title:\n              display: true\n              text: 'Fr\u00e9quence (%)'\n            grid:\n              color: '#e5e5e5'\n              borderDash: [2, 2]\n            ticks:\n              callback: 'formatPercentage'\n            min: 0\n            max: 100\n            beginAtZero: true\n        plugins:\n          legend:\n            position: 'bottom'\n            labels:\n              usePointStyle: false\n              boxWidth: 15\n              padding: 20\n```",
    "bugtrack_url": null,
    "license": "GPL-3.0-or-later",
    "summary": null,
    "version": "0.3.10",
    "project_urls": {
        "Documentation": "https://github.com/niamoto/niamoto#readme",
        "Homepage": "https://github.com/niamoto/niamoto",
        "Repository": "https://github.com/niamoto/niamoto"
    },
    "split_keywords": [
        "niamoto"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a3790560dd6f65b7f3642735462adb63a3fbba2a0426398702beb94cbc0197e8",
                "md5": "e630b4c1bdbe85c7df41f9027d90ec83",
                "sha256": "7adda6adce88f8a96954852a733556b3f15430d33369c85ccc304d15b67e8d94"
            },
            "downloads": -1,
            "filename": "niamoto-0.3.10-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e630b4c1bdbe85c7df41f9027d90ec83",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4,>=3.11",
            "size": 1165341,
            "upload_time": "2025-01-23T22:05:24",
            "upload_time_iso_8601": "2025-01-23T22:05:24.742817Z",
            "url": "https://files.pythonhosted.org/packages/a3/79/0560dd6f65b7f3642735462adb63a3fbba2a0426398702beb94cbc0197e8/niamoto-0.3.10-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "768812c0ecca1667c051276d1edac436a694fcdacad95ae18c1594f6b5eac15f",
                "md5": "a8ed18e6381598ea257e72874d566616",
                "sha256": "cca1aed9142e10afc16f9e97f2175f87db5c2cf6a02a9fce8a73f8908f1e0c83"
            },
            "downloads": -1,
            "filename": "niamoto-0.3.10.tar.gz",
            "has_sig": false,
            "md5_digest": "a8ed18e6381598ea257e72874d566616",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4,>=3.11",
            "size": 1132542,
            "upload_time": "2025-01-23T22:05:27",
            "upload_time_iso_8601": "2025-01-23T22:05:27.278614Z",
            "url": "https://files.pythonhosted.org/packages/76/88/12c0ecca1667c051276d1edac436a694fcdacad95ae18c1594f6b5eac15f/niamoto-0.3.10.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-23 22:05:27",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "niamoto",
    "github_project": "niamoto",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": false,
    "requirements": [],
    "lcname": "niamoto"
}
        
Elapsed time: 0.39701s