From 491b736d07064f653d617ba165ddffa6c8201c0a Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Thu, 28 Sep 2023 13:20:31 +0200
Subject: [PATCH] =?UTF-8?q?Appliquer=20un=20d=C3=A9grad=C3=A9=20sur=20les?=
 =?UTF-8?q?=20mailles=20suivant=20les=20valeurs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 pom.xml                                       |  10 +
 www-client/config/checkstyle-suppressions.xml |   3 +-
 www-client/pom.xml                            |  12 +
 .../www/client/presenter/MapPresenter.java    |   1 +
 .../www/client/ui/AgroclimAppsMenu.java       |   8 +-
 .../www/client/util/color/ColorInterval.java  |  82 ++++++
 .../util/color/ColorSequenceManager.java      | 206 +++++++++++++++
 .../www/client/util/color/ColorSequences.java | 184 ++++++++++++++
 .../www/client/util/color/QuantileType.java   |  98 ++++++++
 .../www/client/util/color/ScaleType.java      |  29 +++
 .../www/client/util/color/package-info.java   |   4 +
 .../www/client/view/LayoutView.java           |   2 +-
 .../agrometinfo/www/client/view/MapView.java  | 134 +++++-----
 .../util/color/ColorSequenceManagerTest.java  | 235 ++++++++++++++++++
 www-server/pom.xml                            |   6 -
 .../www/server/rs/IndicatorResource.java      |   4 +-
 .../agrometinfo/www/shared/dto/ChoiceDTO.java |   6 +-
 17 files changed, 952 insertions(+), 72 deletions(-)
 create mode 100644 www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorInterval.java
 create mode 100644 www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorSequenceManager.java
 create mode 100644 www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorSequences.java
 create mode 100644 www-client/src/main/java/fr/agrometinfo/www/client/util/color/QuantileType.java
 create mode 100644 www-client/src/main/java/fr/agrometinfo/www/client/util/color/ScaleType.java
 create mode 100644 www-client/src/main/java/fr/agrometinfo/www/client/util/color/package-info.java
 create mode 100644 www-client/src/test/java/fr/agrometinfo/www/client/util/color/ColorSequenceManagerTest.java

diff --git a/pom.xml b/pom.xml
index a4c287e..57c6ed1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,16 @@
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
   </properties>
 
+  <dependencies>
+    <!-- Tests -->
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+      <version>${junit.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
   <dependencyManagement>
     <dependencies>
       <!-- ensure all GWT deps use the same version (unless overridden) -->
diff --git a/www-client/config/checkstyle-suppressions.xml b/www-client/config/checkstyle-suppressions.xml
index 4c8e385..68a288d 100644
--- a/www-client/config/checkstyle-suppressions.xml
+++ b/www-client/config/checkstyle-suppressions.xml
@@ -5,4 +5,5 @@
 <suppressions>
     <suppress checks=".*" files="[/\\]generated-sources[/\\]" />
     <suppress checks="JavadocPackage|MagicNumber" files="[/\\]src[/\\]test[/\\]" />
-</suppressions>
\ No newline at end of file
+    <suppress checks="MagicNumber" files="ColorSequences.java" />
+</suppressions>
diff --git a/www-client/pom.xml b/www-client/pom.xml
index 96ebe61..b6f847b 100644
--- a/www-client/pom.xml
+++ b/www-client/pom.xml
@@ -89,6 +89,18 @@
                     <moduleShortName>app</moduleShortName>
                 </configuration>
             </plugin>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <executions>
+                <execution>
+                    <phase>test</phase>
+                    <goals>
+                        <goal>test</goal>
+                    </goals>
+                </execution>
+            </executions>
+        </plugin>
         </plugins>
     </build>
 </project>
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/MapPresenter.java b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/MapPresenter.java
index 18a3ba9..624aa99 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/MapPresenter.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/MapPresenter.java
@@ -86,6 +86,7 @@ public final class MapPresenter implements Presenter {
         request.addQueryParam("year", String.valueOf(choice.getYear()));
         request.addQueryParam("comparison", String.valueOf(choice.getComparison()));
         request.onSuccess(response -> {
+            // TODO view.setChoice(choice);
             view.setGeoJson(response.getBodyAsString());
         }).send();
     }
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/ui/AgroclimAppsMenu.java b/www-client/src/main/java/fr/agrometinfo/www/client/ui/AgroclimAppsMenu.java
index d5816a5..f3c6261 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/ui/AgroclimAppsMenu.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/ui/AgroclimAppsMenu.java
@@ -6,6 +6,7 @@ import org.jboss.elemento.HtmlContentBuilder;
 
 import com.google.gwt.core.client.GWT;
 
+import elemental2.dom.HTMLAnchorElement;
 import elemental2.dom.HTMLLIElement;
 import fr.agrometinfo.www.client.i18n.AppConstants;
 
@@ -47,12 +48,13 @@ public class AgroclimAppsMenu extends Menu<String> {
      */
     private void addMenuItem(final String text, final String logo, final String url) {
         final HtmlContentBuilder<HTMLLIElement> link = Elements.li().css("menu-item simple-menu-item");
-        link.add(Elements.a(url).attr("target", "_blank"));
+        final HtmlContentBuilder<HTMLAnchorElement> elem = Elements.a(url).attr("target", "_blank");
+        link.add(elem);
         if (logo != null) {
-            link.add(Elements.img(logo));
+            elem.add(Elements.img(logo));
             link.add(Elements.div().textContent(text));
         } else {
-            link.textContent(text);
+            elem.textContent(text);
         }
         super.getItemsContainer().add(link);
     }
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorInterval.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorInterval.java
new file mode 100644
index 0000000..2d50a3c
--- /dev/null
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorInterval.java
@@ -0,0 +1,82 @@
+package fr.agrometinfo.www.client.util.color;
+
+/**
+ * Interval (range of values) with associated color.
+ *
+ * @author Olivier Maury
+ */
+public class ColorInterval {
+
+    /**
+     * Hexadecimal value for the color.
+     */
+    private String color;
+    /**
+     * Maximal value for the interval.
+     */
+    private double max;
+    /**
+     * Minimal value for the interval.
+     */
+    private double min;
+    /**
+     * Representation of the interval for quantile (ex.: "0-100%").
+     */
+    private String quantileIntervalValue;
+
+    /**
+     * @return hexadecimal value for the color
+     */
+    public String getColor() {
+        return color;
+    }
+
+    /**
+     * @return Maximal value for the interval.
+     */
+    public double getMax() {
+        return max;
+    }
+
+    /**
+     * @return Minimal value for the interval.
+     */
+    public double getMin() {
+        return min;
+    }
+
+    /**
+     * @return Representation of the interval for quantile (ex.: "0-100%").
+     */
+    public String getQuantileIntervalValue() {
+        return quantileIntervalValue;
+    }
+
+    /**
+     * @param value hexadecimal value for the color
+     */
+    public void setColor(final String value) {
+        this.color = value;
+    }
+
+    /**
+     * @param value Maximal value for the interval.
+     */
+    public void setMax(final double value) {
+        this.max = value;
+    }
+
+    /**
+     * @param value Minimal value for the interval.
+     */
+    public void setMin(final double value) {
+        this.min = value;
+    }
+
+    /**
+     * @param value Representation of the interval for quantile (ex.: "0-100%").
+     */
+    public void setQuantileIntervalValue(final String value) {
+        this.quantileIntervalValue = value;
+    }
+}
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorSequenceManager.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorSequenceManager.java
new file mode 100644
index 0000000..6078948
--- /dev/null
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorSequenceManager.java
@@ -0,0 +1,206 @@
+package fr.agrometinfo.www.client.util.color;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Create colors interval and get colors for a value.
+ *
+ * @author Olivier Maury
+ */
+public final class ColorSequenceManager {
+    /**
+     * Precision to test equality between minNum and maxNum.
+     */
+    private static final double EPSILON = 0.001;
+
+    /**
+     * Get the color of the interval matching the value.
+     *
+     * @param intervals color interval with bounds
+     * @param value     value to search in intervals
+     * @return matching hexadecimal value
+     */
+    public static String getColorForValue(final List<ColorInterval> intervals, final Double value) {
+        final ColorInterval firstInterval = intervals.get(0);
+        if (value <= firstInterval.getMin()) {
+            return firstInterval.getColor();
+        }
+        final ColorInterval lastInterval = intervals.get(intervals.size() - 1);
+        if (value >= lastInterval.getMax()) {
+            return lastInterval.getColor();
+        }
+        return intervals.stream() //
+                .filter(i -> i.getMin() <= value && i.getMax() > value) //
+                .findFirst().orElse(intervals.get(0)).getColor();
+    }
+
+    /**
+     * Color sequence name as defined in {@link ColorSequences}.
+     */
+    private String colorSequenceName;
+    /**
+     * Minimal value, for linear.
+     */
+    private Double minValue;
+    /**
+     * Maximal value, for linear.
+     */
+    private Double maxValue;
+    /**
+     * Scale type.
+     */
+    private ScaleType scaleType;
+    /**
+     * Number of classes, for linear.
+     */
+    private Integer nbOfClasses;
+    /**
+     * All result values, for quantile.
+     */
+    private Collection<Double> results;
+    /**
+     * Quantile type, for quantile.
+     */
+    private QuantileType quantileType;
+
+    /**
+     * @return intervals related to the parameters.
+     */
+    public List<ColorInterval> getColorIntervals() {
+        switch (scaleType) {
+        case LINEAR:
+            return getColorIntervalsForLinear();
+        case QUANTILE:
+            return getColorIntervalsForQuantile();
+        default:
+            throw new UnsupportedOperationException("ScaleType not handled: " + scaleType);
+        }
+    }
+
+    /**
+     * Get intervals for linear representation.
+     *
+     * @return the color intervals
+     */
+    private List<ColorInterval> getColorIntervalsForLinear() {
+        Objects.requireNonNull(minValue);
+        Objects.requireNonNull(maxValue);
+        Objects.requireNonNull(nbOfClasses);
+        if (maxValue < minValue) {
+            throw new IllegalArgumentException("Max value must be superior to min value!");
+        }
+        final List<String> colors = getSequence(nbOfClasses);
+        final List<ColorInterval> colorIntervals = new ArrayList<>();
+        final double step = (maxValue - minValue) / nbOfClasses;
+        double start = minValue;
+        for (int i = 0; i < nbOfClasses; i++) {
+            final double end = start + step;
+            final ColorInterval interval = new ColorInterval();
+            interval.setMin(start);
+            interval.setMax(end);
+            interval.setColor(colors.get(i));
+            colorIntervals.add(interval);
+            start = end;
+        }
+        return colorIntervals;
+    }
+
+    /**
+     * Get intervals for quantile representation.
+     *
+     * @return the colorIntervals
+     */
+    private List<ColorInterval> getColorIntervalsForQuantile() {
+        Objects.requireNonNull(results);
+        Objects.requireNonNull(quantileType);
+        final List<Double> resultList = results.stream().sorted().collect(Collectors.toList());
+        final Double minNum = resultList.get(0);
+        final Double maxNum = resultList.get(resultList.size() - 1);
+
+        final List<ColorInterval> colorIntervals = new ArrayList<>();
+        final List<String> colors = getSequence(quantileType.getNbOfClasses());
+
+        if (Math.abs(maxNum - minNum) < EPSILON) {
+            final ColorInterval interval = new ColorInterval();
+            interval.setMin(minNum);
+            interval.setMax(maxNum);
+            interval.setColor(colors.get(0));
+            interval.setQuantileIntervalValue("0-100%");
+            colorIntervals.add(interval);
+            return colorIntervals;
+        }
+
+        final double hundred = 100.;
+        final int[] bounds = quantileType.getBounds();
+        final int nbOfValues = resultList.size();
+        int lowerIndex = 0;
+        for (int i = 0; i < quantileType.getNbOfClasses(); i++) {
+            int upperIndex = (int) (nbOfValues / hundred * bounds[i]);
+            if (upperIndex > 1) {
+                upperIndex--;
+            }
+            //
+            final ColorInterval interval = new ColorInterval();
+            interval.setMin(resultList.get(lowerIndex));
+            interval.setMax(resultList.get(upperIndex));
+            interval.setColor(colors.get(i));
+            interval.setQuantileIntervalValue(quantileType.getLabels().get(i));
+            colorIntervals.add(interval);
+            lowerIndex = upperIndex;
+        }
+        return colorIntervals;
+    }
+
+    /**
+     * @param nb number of classes
+     * @return color sequence
+     */
+    private List<String> getSequence(final Integer nb) {
+        if (!ColorSequences.SEQUENCES.containsKey(colorSequenceName)) {
+            throw new IllegalArgumentException("Unknown color sequence " + colorSequenceName);
+        }
+        if (!ColorSequences.SEQUENCES.get(colorSequenceName).containsKey(nb)) {
+            throw new IllegalArgumentException(
+                    "Unknown color sequence " + colorSequenceName + " for nbOfClasses=" + nb);
+        }
+        return ColorSequences.SEQUENCES.get(colorSequenceName).get(nb);
+    }
+
+    /**
+     * @param value color sequence, must match {@link ColorSequences#SEQUENCES}.
+     */
+    public void setColorSequenceName(final String value) {
+        this.colorSequenceName = value;
+    }
+
+    /**
+     * Parameterize for {@link ScaleType#LINEAR}.
+     *
+     * @param min minimal value
+     * @param max maximal value
+     * @param nb  number of classes, must match {@link ColorSequences#SEQUENCES}
+     *            according to {@link #colorSequenceName}.
+     */
+    public void setLinear(final double min, final double max, final int nb) {
+        this.minValue = min;
+        this.maxValue = max;
+        this.nbOfClasses = nb;
+        this.scaleType = ScaleType.LINEAR;
+    }
+
+    /**
+     * Parameterize for {@link ScaleType#QUANTILE}.
+     *
+     * @param type   quantile type
+     * @param values values to compute quantiles
+     */
+    public void setQuantile(final QuantileType type, final Collection<Double> values) {
+        this.quantileType = type;
+        this.results = values;
+        this.scaleType = ScaleType.QUANTILE;
+    }
+}
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorSequences.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorSequences.java
new file mode 100644
index 0000000..c44f0c0
--- /dev/null
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ColorSequences.java
@@ -0,0 +1,184 @@
+package fr.agrometinfo.www.client.util.color;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Colors according to the number of classes to display.
+ *
+ * Do not edit this file, it was created on 2022-04-27T15:32:31.447659 using the
+ * command siclima-client/bin/colors2java.py.
+ */
+public abstract class ColorSequences {
+
+    /**
+     * Colors sequences: name ⮕ (nbOfClasses ⮕ colors).
+     */
+    public static final Map<String, Map<Integer, List<String>>> SEQUENCES = new LinkedHashMap<>();
+
+    static {
+        SEQUENCES.put("Blues", new HashMap<>());
+        SEQUENCES.get("Blues").put(3, Arrays.asList("def7ff", "9eebff", "2ebaff"));
+        SEQUENCES.get("Blues").put(4, Arrays.asList("ebfaff", "b8edff", "6edbff", "19a8ff"));
+        SEQUENCES.get("Blues").put(5, Arrays.asList("ebfaff", "b8edff", "6edbff", "2ebaff", "0082ed"));
+        SEQUENCES.get("Blues").put(6, Arrays.asList("ebfaff", "c2f0ff", "9eebff", "6edbff", "2ebaff", "0082ed"));
+        SEQUENCES.get("Blues").put(7,
+                Arrays.asList("ebfaff", "c2f0ff", "9eebff", "6edbff", "40c7ff", "19a8ff", "006df2"));
+        SEQUENCES.get("Blues").put(8,
+                Arrays.asList("f7fcff", "def7ff", "c2f0ff", "9eebff", "6edbff", "40c7ff", "19a8ff", "006df2"));
+        SEQUENCES.get("Blues").put(9, Arrays.asList("f7fcff", "def7ff", "c2f0ff", "9eebff", "6edbff", "40c7ff",
+                "19a8ff", "0082ed", "0050b2"));
+        SEQUENCES.put("BluesReversed", new HashMap<>());
+        SEQUENCES.get("BluesReversed").put(3, Arrays.asList("2ebaff", "9eebff", "def7ff"));
+        SEQUENCES.get("BluesReversed").put(4, Arrays.asList("19a8ff", "6edbff", "b8edff", "ebfaff"));
+        SEQUENCES.get("BluesReversed").put(5, Arrays.asList("0082ed", "2ebaff", "6edbff", "b8edff", "ebfaff"));
+        SEQUENCES.get("BluesReversed").put(6,
+                Arrays.asList("0082ed", "2ebaff", "6edbff", "9eebff", "c2f0ff", "ebfaff"));
+        SEQUENCES.get("BluesReversed").put(7,
+                Arrays.asList("006df2", "19a8ff", "40c7ff", "6edbff", "9eebff", "c2f0ff", "ebfaff"));
+        SEQUENCES.get("BluesReversed").put(8,
+                Arrays.asList("006df2", "19a8ff", "40c7ff", "6edbff", "9eebff", "c2f0ff", "def7ff", "f7fcff"));
+        SEQUENCES.get("BluesReversed").put(9, Arrays.asList("0050b2", "0082ed", "19a8ff", "40c7ff", "6edbff", "9eebff",
+                "c2f0ff", "def7ff", "f7fcff"));
+        SEQUENCES.put("Greens", new HashMap<>());
+        SEQUENCES.get("Greens").put(3, Arrays.asList("e6ffe6", "a1ffa1", "30ff3d"));
+        SEQUENCES.get("Greens").put(4, Arrays.asList("edffed", "baffba", "73ff73", "29e62b"));
+        SEQUENCES.get("Greens").put(5, Arrays.asList("edffed", "baffba", "73ff73", "30ff3d", "00cc00"));
+        SEQUENCES.get("Greens").put(6, Arrays.asList("edffed", "c7ffc7", "a1ffa1", "73ff73", "30ff3d", "00cc00"));
+        SEQUENCES.get("Greens").put(7,
+                Arrays.asList("edffed", "c7ffc7", "a1ffa1", "73ff73", "40ff4d", "21e62b", "00a619"));
+        SEQUENCES.get("Greens").put(8,
+                Arrays.asList("f7fff7", "e6ffe6", "c7ffc7", "a1ffa1", "73ff73", "40ff4d", "21e62b", "00a619"));
+        SEQUENCES.get("Greens").put(9, Arrays.asList("f7fff7", "e6ffe6", "c7ffc7", "a1ffa1", "73ff73", "40ff4d",
+                "21e62b", "00cc00", "008000"));
+        SEQUENCES.put("GreensReversed", new HashMap<>());
+        SEQUENCES.get("GreensReversed").put(3, Arrays.asList("30ff3d", "a1ffa1", "e6ffe6"));
+        SEQUENCES.get("GreensReversed").put(4, Arrays.asList("29e62b", "73ff73", "baffba", "edffed"));
+        SEQUENCES.get("GreensReversed").put(5, Arrays.asList("00cc00", "30ff3d", "73ff73", "baffba", "edffed"));
+        SEQUENCES.get("GreensReversed").put(6,
+                Arrays.asList("00cc00", "30ff3d", "73ff73", "a1ffa1", "c7ffc7", "edffed"));
+        SEQUENCES.get("GreensReversed").put(7,
+                Arrays.asList("00a619", "21e62b", "40ff4d", "73ff73", "a1ffa1", "c7ffc7", "edffed"));
+        SEQUENCES.get("GreensReversed").put(8,
+                Arrays.asList("00a619", "21e62b", "40ff4d", "73ff73", "a1ffa1", "c7ffc7", "e6ffe6", "f7fff7"));
+        SEQUENCES.get("GreensReversed").put(9, Arrays.asList("008000", "00cc00", "21e62b", "40ff4d", "73ff73", "a1ffa1",
+                "c7ffc7", "e6ffe6", "f7fff7"));
+        SEQUENCES.put("Precipitation", new HashMap<>());
+        SEQUENCES.get("Precipitation").put(5, Arrays.asList("a6611a", "dfc27d", "f5f5f5", "80cdc1", "018571"));
+        SEQUENCES.get("Precipitation").put(6,
+                Arrays.asList("8c510a", "d8b365", "f6e8c3", "c7eae5", "5ab4ac", "01665e"));
+        SEQUENCES.get("Precipitation").put(7,
+                Arrays.asList("8c510a", "d8b365", "f6e8c3", "f5f5f5", "c7eae5", "5ab4ac", "01665e"));
+        SEQUENCES.get("Precipitation").put(8,
+                Arrays.asList("8c510a", "bf812d", "dfc27d", "f6e8c3", "c7eae5", "80cdc1", "35978f", "01665e"));
+        SEQUENCES.get("Precipitation").put(9, Arrays.asList("8c510a", "bf812d", "dfc27d", "f6e8c3", "f5f5f5", "c7eae5",
+                "80cdc1", "35978f", "01665e"));
+        SEQUENCES.get("Precipitation").put(10, Arrays.asList("543005", "8c510a", "bf812d", "dfc27d", "f6e8c3", "c7eae5",
+                "80cdc1", "35978f", "01665e", "003c30"));
+        SEQUENCES.get("Precipitation").put(11, Arrays.asList("543005", "8c510a", "bf812d", "dfc27d", "f6e8c3", "f5f5f5",
+                "c7eae5", "80cdc1", "35978f", "01665e", "003c30"));
+        SEQUENCES.put("PrecipitationReversed", new HashMap<>());
+        SEQUENCES.get("PrecipitationReversed").put(5, Arrays.asList("018571", "80cdc1", "f5f5f5", "dfc27d", "a6611a"));
+        SEQUENCES.get("PrecipitationReversed").put(6,
+                Arrays.asList("01665e", "5ab4ac", "c7eae5", "f6e8c3", "d8b365", "8c510a"));
+        SEQUENCES.get("PrecipitationReversed").put(7,
+                Arrays.asList("01665e", "5ab4ac", "c7eae5", "f5f5f5", "f6e8c3", "d8b365", "8c510a"));
+        SEQUENCES.get("PrecipitationReversed").put(8,
+                Arrays.asList("01665e", "35978f", "80cdc1", "c7eae5", "f6e8c3", "dfc27d", "bf812d", "8c510a"));
+        SEQUENCES.get("PrecipitationReversed").put(9, Arrays.asList("01665e", "35978f", "80cdc1", "c7eae5", "f5f5f5",
+                "f6e8c3", "dfc27d", "bf812d", "8c510a"));
+        SEQUENCES.get("PrecipitationReversed").put(10, Arrays.asList("003c30", "01665e", "35978f", "80cdc1", "c7eae5",
+                "f6e8c3", "dfc27d", "bf812d", "8c510a", "543005"));
+        SEQUENCES.get("PrecipitationReversed").put(11, Arrays.asList("003c30", "01665e", "35978f", "80cdc1", "c7eae5",
+                "f5f5f5", "f6e8c3", "dfc27d", "bf812d", "8c510a", "543005"));
+        SEQUENCES.put("RdBu", new HashMap<>());
+        SEQUENCES.get("RdBu").put(3, Arrays.asList("f28c80", "f7f7f7", "66d9ff"));
+        SEQUENCES.get("RdBu").put(4, Arrays.asList("cc0040", "f7a69e", "91ebff", "00b2ff"));
+        SEQUENCES.get("RdBu").put(5, Arrays.asList("cc0040", "f7a69e", "f7f7f7", "91ebff", "00b2ff"));
+        SEQUENCES.get("RdBu").put(6, Arrays.asList("b2194d", "f28c80", "ffdbd6", "d1f5ff", "66d9ff", "1999ff"));
+        SEQUENCES.get("RdBu").put(7,
+                Arrays.asList("b2194d", "f28c80", "ffdbd6", "f7f7f7", "d1f5ff", "66d9ff", "1999ff"));
+        SEQUENCES.get("RdBu").put(8,
+                Arrays.asList("b2194d", "d9666e", "f7a69e", "ffdbd6", "d1f5ff", "91ebff", "40ccff", "1999ff"));
+        SEQUENCES.get("RdBu").put(9, Arrays.asList("b2194d", "d9666e", "f7a69e", "ffdbd6", "f7f7f7", "d1f5ff", "91ebff",
+                "40ccff", "1999ff"));
+        SEQUENCES.get("RdBu").put(10, Arrays.asList("660040", "b2194d", "d9666e", "f7a69e", "ffdbd6", "d1f5ff",
+                "91ebff", "40ccff", "1999ff", "004c99"));
+        SEQUENCES.get("RdBu").put(11, Arrays.asList("660040", "b2194d", "d9666e", "f7a69e", "ffdbd6", "f7f7f7",
+                "d1f5ff", "91ebff", "40ccff", "1999ff", "004c99"));
+        SEQUENCES.put("RdBuReversed", new HashMap<>());
+        SEQUENCES.get("RdBuReversed").put(3, Arrays.asList("66d9ff", "f7f7f7", "f28c80"));
+        SEQUENCES.get("RdBuReversed").put(4, Arrays.asList("00b2ff", "91ebff", "f7a69e", "cc0040"));
+        SEQUENCES.get("RdBuReversed").put(5, Arrays.asList("00b2ff", "91ebff", "f7f7f7", "f7a69e", "cc0040"));
+        SEQUENCES.get("RdBuReversed").put(6, Arrays.asList("1999ff", "66d9ff", "d1f5ff", "ffdbd6", "f28c80", "b2194d"));
+        SEQUENCES.get("RdBuReversed").put(7,
+                Arrays.asList("1999ff", "66d9ff", "d1f5ff", "f7f7f7", "ffdbd6", "f28c80", "b2194d"));
+        SEQUENCES.get("RdBuReversed").put(8,
+                Arrays.asList("1999ff", "40ccff", "91ebff", "d1f5ff", "ffdbd6", "f7a69e", "d9666e", "b2194d"));
+        SEQUENCES.get("RdBuReversed").put(9, Arrays.asList("1999ff", "40ccff", "91ebff", "d1f5ff", "f7f7f7", "ffdbd6",
+                "f7a69e", "d9666e", "b2194d"));
+        SEQUENCES.get("RdBuReversed").put(10, Arrays.asList("004c99", "1999ff", "40ccff", "91ebff", "d1f5ff", "ffdbd6",
+                "f7a69e", "d9666e", "b2194d", "660040"));
+        SEQUENCES.get("RdBuReversed").put(11, Arrays.asList("004c99", "1999ff", "40ccff", "91ebff", "d1f5ff", "f7f7f7",
+                "ffdbd6", "f7a69e", "d9666e", "b2194d", "660040"));
+        SEQUENCES.put("Temperature", new HashMap<>());
+        SEQUENCES.get("Temperature").put(5, Arrays.asList("ca0020", "f4a582", "f7f7f7", "92c5de", "0571b0"));
+        SEQUENCES.get("Temperature").put(6, Arrays.asList("b2182b", "ef8a62", "fddbc7", "d1e5f0", "67a9cf", "2166ac"));
+        SEQUENCES.get("Temperature").put(7,
+                Arrays.asList("b2182b", "ef8a62", "fddbc7", "f7f7f7", "d1e5f0", "67a9cf", "2166ac"));
+        SEQUENCES.get("Temperature").put(8,
+                Arrays.asList("b2182b", "d6604d", "f4a582", "fddbc7", "d1e5f0", "92c5de", "4393c3", "2166ac"));
+        SEQUENCES.get("Temperature").put(9, Arrays.asList("b2182b", "d6604d", "f4a582", "fddbc7", "f7f7f7", "d1e5f0",
+                "92c5de", "4393c3", "2166ac"));
+        SEQUENCES.get("Temperature").put(10, Arrays.asList("67001f", "b2182b", "d6604d", "f4a582", "fddbc7", "d1e5f0",
+                "92c5de", "4393c3", "2166ac", "053061"));
+        SEQUENCES.get("Temperature").put(11, Arrays.asList("67001f", "b2182b", "d6604d", "f4a582", "fddbc7", "f7f7f7",
+                "d1e5f0", "92c5de", "4393c3", "2166ac", "053061"));
+        SEQUENCES.put("TemperatureReversed", new HashMap<>());
+        SEQUENCES.get("TemperatureReversed").put(5, Arrays.asList("0571b0", "92c5de", "f7f7f7", "f4a582", "ca0020"));
+        SEQUENCES.get("TemperatureReversed").put(6,
+                Arrays.asList("2166ac", "67a9cf", "d1e5f0", "fddbc7", "ef8a62", "b2182b"));
+        SEQUENCES.get("TemperatureReversed").put(7,
+                Arrays.asList("2166ac", "67a9cf", "d1e5f0", "f7f7f7", "fddbc7", "ef8a62", "b2182b"));
+        SEQUENCES.get("TemperatureReversed").put(8,
+                Arrays.asList("2166ac", "4393c3", "92c5de", "d1e5f0", "fddbc7", "f4a582", "d6604d", "b2182b"));
+        SEQUENCES.get("TemperatureReversed").put(9, Arrays.asList("2166ac", "4393c3", "92c5de", "d1e5f0", "f7f7f7",
+                "fddbc7", "f4a582", "d6604d", "b2182b"));
+        SEQUENCES.get("TemperatureReversed").put(10, Arrays.asList("053061", "2166ac", "4393c3", "92c5de", "d1e5f0",
+                "fddbc7", "f4a582", "d6604d", "b2182b", "67001f"));
+        SEQUENCES.get("TemperatureReversed").put(11, Arrays.asList("053061", "2166ac", "4393c3", "92c5de", "d1e5f0",
+                "f7f7f7", "fddbc7", "f4a582", "d6604d", "b2182b", "67001f"));
+        SEQUENCES.put("YlOrRd", new HashMap<>());
+        SEQUENCES.get("YlOrRd").put(3, Arrays.asList("ffeda6", "ffb259", "f23b33"));
+        SEQUENCES.get("YlOrRd").put(4, Arrays.asList("ffffb2", "ffcc66", "ff8c4d", "e61933"));
+        SEQUENCES.get("YlOrRd").put(5, Arrays.asList("ffffb2", "ffcc66", "ff8c4d", "f23b33", "bf004d"));
+        SEQUENCES.get("YlOrRd").put(6, Arrays.asList("ffffb2", "ffd980", "ffb259", "ff8c4d", "f23b33", "bf004d"));
+        SEQUENCES.get("YlOrRd").put(7,
+                Arrays.asList("ffffb2", "ffd980", "ffb259", "ff8c4d", "ff4d40", "e61933", "b2004d"));
+        SEQUENCES.get("YlOrRd").put(8,
+                Arrays.asList("ffffcc", "ffeda6", "ffd980", "ffb259", "ff8c4d", "ff4d40", "e61933", "b2004d"));
+        SEQUENCES.get("YlOrRd").put(9, Arrays.asList("ffffcc", "ffeda6", "ffd980", "ffb259", "ff8c4d", "ff4d40",
+                "e61933", "bf004d", "80004d"));
+        SEQUENCES.put("YlOrRdReversed", new HashMap<>());
+        SEQUENCES.get("YlOrRdReversed").put(3, Arrays.asList("f23b33", "ffb259", "ffeda6"));
+        SEQUENCES.get("YlOrRdReversed").put(4, Arrays.asList("e61933", "ff8c4d", "ffcc66", "ffffb2"));
+        SEQUENCES.get("YlOrRdReversed").put(5, Arrays.asList("bf004d", "f23b33", "ff8c4d", "ffcc66", "ffffb2"));
+        SEQUENCES.get("YlOrRdReversed").put(6,
+                Arrays.asList("bf004d", "f23b33", "ff8c4d", "ffb259", "ffd980", "ffffb2"));
+        SEQUENCES.get("YlOrRdReversed").put(7,
+                Arrays.asList("b2004d", "e61933", "ff4d40", "ff8c4d", "ffb259", "ffd980", "ffffb2"));
+        SEQUENCES.get("YlOrRdReversed").put(8,
+                Arrays.asList("b2004d", "e61933", "ff4d40", "ff8c4d", "ffb259", "ffd980", "ffeda6", "ffffcc"));
+        SEQUENCES.get("YlOrRdReversed").put(9, Arrays.asList("80004d", "bf004d", "e61933", "ff4d40", "ff8c4d", "ffb259",
+                "ffd980", "ffeda6", "ffffcc"));
+    }
+
+    /**
+     * No constructor for helper class.
+     */
+    private ColorSequences() {
+    }
+}
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/color/QuantileType.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/QuantileType.java
new file mode 100644
index 0000000..29de90f
--- /dev/null
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/QuantileType.java
@@ -0,0 +1,98 @@
+package fr.agrometinfo.www.client.util.color;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import fr.agrometinfo.www.client.i18n.AppConstants;
+
+/**
+ * Type of quantile for graphic representation of indicator calculation results.
+ */
+public enum QuantileType {
+    /**
+     * 5%.
+     */
+    CENTILES_05(5, 95, 100), //
+    /**
+     * Common quantiles.
+     */
+    QUANTILES(1, 5, 25, 50, 75, 95, 99, 100), //
+    /**
+     * Each 10%.
+     */
+    DECILES(10, 20, 30, 40, 50, 60, 70, 80, 90, 100), //
+    /**
+     * Each 20%.
+     */
+    QUINTILES(20, 40, 60, 80, 100), //
+    /**
+     * Each 25%.
+     */
+    QUARTILES(25, 50, 75, 100);
+
+    /**
+     * @param nbOfClasses number of classes
+     * @return the quantile for the number of classes
+     */
+    public static QuantileType valueOf(final int nbOfClasses) {
+        for (final QuantileType q : QuantileType.values()) {
+            if (q.getNbOfClasses() == nbOfClasses) {
+                return q;
+            }
+        }
+        throw new IllegalArgumentException();
+    }
+
+    /**
+     * Bounds of classes.
+     */
+    private final int[] bounds;
+
+    /**
+     * The labels for the classes.
+     */
+    private final List<String> labels = new ArrayList<>();
+
+    /**
+     * Constructor.
+     *
+     * @param values bounds of the classes
+     */
+    QuantileType(final int... values) {
+        bounds = values;
+        int prev = 0;
+        for (final int value : values) {
+            labels.add(prev + "-" + value + "%");
+            prev = value;
+        }
+    }
+
+    /**
+     * @return bounds of the classes
+     */
+    public int[] getBounds() {
+        return bounds;
+    }
+
+    /**
+     * @return Lookup key for {@link AppConstants}.
+     */
+    public String getI18nKey() {
+        return getClass().getName().concat(this.name().toLowerCase());
+    }
+
+    /**
+     * @return labels for the classes
+     */
+    public List<String> getLabels() {
+        return labels;
+    }
+
+    /**
+     * @return number of classes.
+     */
+    public int getNbOfClasses() {
+        return bounds.length;
+    }
+}
+
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ScaleType.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ScaleType.java
new file mode 100644
index 0000000..7f820c2
--- /dev/null
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/ScaleType.java
@@ -0,0 +1,29 @@
+package fr.agrometinfo.www.client.util.color;
+
+import fr.agrometinfo.www.client.i18n.AppConstants;
+
+/**
+ * The user can choice the color scale : linear or quantile.
+ *
+ * This enum is used in the result overview in the map.
+ *
+ * @author Olivier Maury
+ */
+public enum ScaleType {
+    /**
+     * Linear scale.
+     */
+    LINEAR,
+    /**
+     * Quantile scale.
+     */
+    QUANTILE;
+
+    /**
+     * @return Lookup key for {@link AppConstants}.
+     */
+    public String getI18nKey() {
+        return getClass().getName().concat(this.name().toLowerCase());
+    }
+}
+
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/color/package-info.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/package-info.java
new file mode 100644
index 0000000..fe66df9
--- /dev/null
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/color/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Color managing for the map cells.
+ */
+package fr.agrometinfo.www.client.util.color;
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java b/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java
index 94fb0fc..b555737 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java
@@ -355,7 +355,7 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
         }
     }
 
-    private void onComparisonChange(final Boolean newValue) {
+    private void onComparisonChange(final boolean newValue) {
         choice.setComparison(newValue);
         onChoiceChange();
     }
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/view/MapView.java b/www-client/src/main/java/fr/agrometinfo/www/client/view/MapView.java
index 0364b27..ea5fc75 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/view/MapView.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/view/MapView.java
@@ -8,6 +8,7 @@ import org.geojson.FeatureCollection;
 import org.jboss.elemento.HtmlContentBuilder;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsonUtils;
 
 import elemental2.dom.HTMLDivElement;
@@ -17,6 +18,8 @@ import fr.agrometinfo.www.client.presenter.MapPresenter;
 import fr.agrometinfo.www.client.ui.map.ControlSuppliers;
 import fr.agrometinfo.www.client.ui.map.TileSuppliers;
 import fr.agrometinfo.www.client.util.ApplicationUtils;
+import fr.agrometinfo.www.client.util.color.ColorInterval;
+import fr.agrometinfo.www.client.util.color.ColorSequenceManager;
 import ol.Collection;
 import ol.Coordinate;
 import ol.Feature;
@@ -46,7 +49,6 @@ import ol.style.Stroke;
 import ol.style.StrokeOptions;
 import ol.style.Style;
 import ol.style.StyleOptions;
-import ol.style.Text;
 
 /**
  * View for the embedded map component.
@@ -110,7 +112,6 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
         baseLayers.push(TileSuppliers.createOpenTopoMap());
         baseLayers.push(TileSuppliers.createPiano());
         baseLayers.push(TileSuppliers.createOsm());
-        baseLayers.push(TileSuppliers.createOsm());
         baseLayerGroupOptions.setLayers(baseLayers);
         final Group baseLayerGroup = new Group(baseLayerGroupOptions);
         baseLayerGroup.set(TITLE, CSTS.mapBaseLayer());
@@ -138,21 +139,7 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
         GWT.log("MapView.addOverlays() done");
     }
 
-    private static Feature[] colorizeFeatures(final Feature[] features) {
-        for (final Feature f : features) {
-            final Color foreground = Color.getColorFromString("#ff0000");
-            // TODO : get value.
-            final Style style = createCellStyle(foreground, foreground, "1");
-            f.setStyle(style);
-        }
-        return features;
-    }
-
     private static Style createCellStyle(final Color foreground, final Color background) {
-        return createCellStyle(foreground, background, null);
-    }
-
-    private static Style createCellStyle(final Color foreground, final Color background, final String cellIdInString) {
         final FillOptions fillOption = new FillOptions();
         fillOption.setColor(background);
         final Fill fill = new Fill(fillOption);
@@ -160,11 +147,6 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
         final StrokeOptions strokeOptions = new StrokeOptions();
         strokeOptions.setColor(foreground);
         final StyleOptions styleOption = new StyleOptions();
-        if (cellIdInString != null) {
-            final Text t = new Text();
-            t.setText(cellIdInString);
-            styleOption.setText(t);
-        }
         styleOption.setFill(fill);
         styleOption.setStroke(new Stroke(strokeOptions));
 
@@ -173,26 +155,32 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
 
     /**
      * @param target The id of the container element for the map.
-     * @param view view of map
+     * @param view   view of map
      * @return common options for MapWidget, MapForPreviewCellWidget and
-     * MapResultWidgets
+     *         MapResultWidgets
      */
-    private static MapOptions createMapOptions(final String target,
-            final View view) {
+    private static MapOptions createMapOptions(final String target, final View view) {
         GWT.log("MapView.createMapOptions() " + target);
         final MapOptions options = new MapOptions();
         options.setTarget(target);
         options.setView(view);
         // add some controls
         options.setControls(ControlSuppliers.createAllControls());
-
-        final Collection<Base> lstLayer = new Collection<>();
-        // lstLayer.push(vectorLayer);
-        options.setLayers(lstLayer);
-
+        options.setLayers(new Collection<>());
         return options;
     }
 
+    private static Vector createVectorLayer(final ol.source.Vector vectorSource) {
+        final VectorLayerOptions vectorLayerOptions = new VectorLayerOptions();
+        vectorLayerOptions.setSource(vectorSource);
+        setRenderModeImage(vectorLayerOptions);
+        final Vector vectorLayer = new Vector(vectorLayerOptions);
+        vectorLayer.setVisible(true);
+        vectorLayer.setOpacity(INDICATOR_LAYER_OPACITY);
+        vectorLayer.setZIndex(INDICATOR_LAYER_ZINDEX);
+        return vectorLayer;
+    }
+
     /**
      * @return attributions for map.
      */
@@ -200,8 +188,13 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
         return "© INRAE AgroClim - AgroMetInfo - " + ApplicationUtils.getVersion();
     }
 
+    private static double getValue(final Feature f) {
+        return Double.parseDouble(f.getProperties().getAsAny("value").asString());
+    }
+
     private static Feature[] parseGeoJson(final String geoJson) {
-        final Object dataGeoJson = JsonUtils.unsafeEval(geoJson);
+        GWT.log("MapView.parseGeoJson()");
+        final JavaScriptObject dataGeoJson = JsonUtils.unsafeEval(geoJson);
         // create Feature and after geojson layer (Vector layer)
         final GeoJson geoJsonFormat = OLFactory.createGeoJSON();
 
@@ -209,7 +202,6 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
         projOpt.setCode(EPSG_3857);
         final GeoJsonFeatureOptions geoJsonFeatureOptions = new GeoJsonFeatureOptions();
         geoJsonFeatureOptions.setFeatureProjection(new Projection(projOpt));
-
         return geoJsonFormat.readFeatures(dataGeoJson, geoJsonFeatureOptions);
     }
 
@@ -219,7 +211,7 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
      * @param v options for the vector layer
      */
     public static native void setRenderModeImage(VectorLayerOptions v) /*-{
-    v.renderMode = 'image';
+        v.renderMode = 'image';
     }-*/;
 
     /**
@@ -228,18 +220,23 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
      * @param sel
      */
     private static native void setRenderSelModeImage(Select sel) /*-{
-    for ( var propSel in sel) {
-      if (sel[propSel] != null && sel[propSel]['type'] == 'VECTOR') {
-        for ( var prop in sel[propSel]) {
-          if (sel[propSel][prop] == 'vector') {
-            sel[propSel][prop] = 'image';
-            return;
-          }
+        for ( var propSel in sel) {
+            if (sel[propSel] != null && sel[propSel]['type'] == 'VECTOR') {
+                for ( var prop in sel[propSel]) {
+                    if (sel[propSel][prop] == 'vector') {
+                        sel[propSel][prop] = 'image';
+                        return;
+                    }
+                }
+            }
         }
-      }
-    }
     }-*/;
 
+    /**
+     * Layer with cells.
+     */
+    private Vector vectorLayer;
+
     /**
      * Zoom at map creation.
      */
@@ -260,6 +257,11 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
      */
     private View view;
 
+    /**
+     * Color intervals used to set background color of cells.
+     */
+    private List<ColorInterval> colorIntervals;
+
     /**
      * Constructor.
      *
@@ -276,7 +278,7 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
         final SelectOptions selectOptions = new SelectOptions();
         selectOptions.setCondition(Condition.getClick());
         final Style style = createCellStyle(Color.getColorFromString("blue"), new Color(255, 0, 0, 0.5f));
-        selectOptions.setStyle((f, r) -> new Style[]{style});
+        selectOptions.setStyle((f, r) -> new Style[] {style});
 
         // create a select interaction
         final Select featureSelect = new Select(selectOptions);
@@ -290,15 +292,16 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
         removeContextMenuRightClick();
     }
 
-    private Vector createVectorLayer(final ol.source.Vector vectorSource) {
-        final VectorLayerOptions vectorLayerOptions = new VectorLayerOptions();
-        vectorLayerOptions.setSource(vectorSource);
-        setRenderModeImage(vectorLayerOptions);
-        final Vector vectorLayer = new Vector(vectorLayerOptions);
-        vectorLayer.setVisible(true);
-        vectorLayer.setOpacity(INDICATOR_LAYER_OPACITY);
-        vectorLayer.setZIndex(INDICATOR_LAYER_ZINDEX);
-        return vectorLayer;
+    private Feature[] colorizeFeatures(final Feature[] features) {
+        for (final Feature f : features) {
+            final Double value = getValue(f);
+            final String color = "#" + ColorSequenceManager.getColorForValue(colorIntervals, value);
+            GWT.log("Feature #" + f.getId() + ", value=" + value + ", color=" + color);
+            final Color foreground = Color.getColorFromString(color);
+            final Style style = createCellStyle(foreground, foreground);
+            f.setStyle(style);
+        }
+        return features;
     }
 
     private ol.source.Vector createVectorSource(final Feature[] colorizedFeatures) {
@@ -373,14 +376,13 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
     }
 
     private void setFeatures(final Feature[] features) {
-        /*-
+        GWT.log("MapView.setFeatures()");
         if (vectorLayer != null) {
-            openLayerMap.removeLayer(vectorLayer);
+            map.removeLayer(vectorLayer);
         }
-         */
         final Feature[] colorizedFeatures = colorizeFeatures(features);
         final ol.source.Vector vectorSource = createVectorSource(colorizedFeatures);
-        final Vector vectorLayer = createVectorLayer(vectorSource);
+        vectorLayer = createVectorLayer(vectorSource);
         map.addLayer(vectorLayer);
         map.addControl(ControlSuppliers.createZoomToExtent(vectorSource.getExtent()));
         // Only zoom on the results the first time
@@ -391,7 +393,27 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma
 
     @Override
     public void setGeoJson(final String geoJson) {
+        GWT.log("MapView.setGeoJson()");
         final Feature[] features = parseGeoJson(geoJson);
+        GWT.log("MapView.setGeoJson() nb of features: " + features.length);
+        //
+        double min = Double.MAX_VALUE;
+        double max = Double.MIN_VALUE;
+        for (final Feature f : features) {
+            final double value = getValue(f);
+            if (min > value) {
+                min = value;
+            }
+            if (max < value) {
+                max = value;
+            }
+        }
+        final int nbOfClasses = 5;
+        final ColorSequenceManager colorMgr = new ColorSequenceManager();
+        colorMgr.setColorSequenceName("RdBuReversed");
+        colorMgr.setLinear(min, max, nbOfClasses);
+        colorIntervals = colorMgr.getColorIntervals();
+        //
         setFeatures(features);
     }
 
diff --git a/www-client/src/test/java/fr/agrometinfo/www/client/util/color/ColorSequenceManagerTest.java b/www-client/src/test/java/fr/agrometinfo/www/client/util/color/ColorSequenceManagerTest.java
new file mode 100644
index 0000000..1666d03
--- /dev/null
+++ b/www-client/src/test/java/fr/agrometinfo/www/client/util/color/ColorSequenceManagerTest.java
@@ -0,0 +1,235 @@
+package fr.agrometinfo.www.client.util.color;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test {@link ColorsSequenceManager}.
+ *
+ * Last change $Date: 2022-04-27 15:40:18 +0200 (mer. 27 avril 2022) $
+ *
+ * @author omaury
+ * @author $Author: omaury $
+ * @version $Revision: 904 $
+ */
+public class ColorSequenceManagerTest {
+
+    /**
+     * A color sequence with 10 colors.
+     */
+    private static final String NAME = "RdBu";
+
+    /**
+     * Values to render.
+     */
+    private static final List<Double> RESULTS = IntStream.rangeClosed(1, 5630).asDoubleStream().boxed()
+            .collect(Collectors.toList());
+
+    private static void checkColors(final List<ColorInterval> intervals, final Double value) {
+        final String expectedColor = ColorSequenceManager.getColorForValue(intervals, value);
+
+        IntStream.range(value.intValue(), value.intValue() + 9)//
+        .forEach(v -> {
+            final String colorAt = ColorSequenceManager.getColorForValue(intervals, (double) v);
+            assertEquals(expectedColor, colorAt);
+        });
+    }
+
+    @Test
+    void checkValuesForAllIntervals() {
+        final int maxValue = 100;
+        final int minValue = 10;
+        final int nbOfClasses = 10;
+
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        mgr.setLinear(minValue, maxValue, nbOfClasses);
+        mgr.setColorSequenceName(NAME);
+        final List<ColorInterval> intervals = mgr.getColorIntervals();
+        assertEquals(nbOfClasses, intervals.size());
+
+        for (int number = minValue; number < maxValue; number += 9) {
+            checkColors(intervals, (double) number);
+        }
+    }
+
+    @Test
+    void getColorIntervalsQuantile() {
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        for (final QuantileType quantile : QuantileType.values()) {
+            final int nbOfClasses = quantile.getNbOfClasses();
+            mgr.setQuantile(quantile, RESULTS);
+            mgr.setColorSequenceName(NAME);
+            final List<ColorInterval> intervals = mgr.getColorIntervals();
+            assertEquals(nbOfClasses, intervals.size());
+        }
+    }
+
+    @Test
+    void intervalBounds() {
+        final double minValue = 9.8;
+        final double maxValue = 100.2;
+        final int nbOfClasses = 10;
+
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        mgr.setLinear(minValue, maxValue, nbOfClasses);
+        mgr.setColorSequenceName(NAME);
+        final List<ColorInterval> intervals = mgr.getColorIntervals();
+        assertNotNull(intervals);
+        assertEquals(nbOfClasses, intervals.size());
+
+        for (double number = minValue; number < maxValue; number++) {
+            final String actual = ColorSequenceManager.getColorForValue(intervals, number);
+            assertNotNull(actual);
+        }
+
+        final ColorInterval firstInterval = intervals.get(0);
+        assertEquals(minValue, firstInterval.getMin(), 0.1);
+
+        final ColorInterval lastInterval = intervals.get(intervals.size() - 1);
+        assertEquals(maxValue, lastInterval.getMax(), 0.1);
+    }
+
+    @Test
+    void maxValueForLinear() throws Exception {
+        final double maxValue = 97.94764038901506;
+        final double minValue = 40.48128240581562;
+        final double value = maxValue;
+        final int nbOfClasses = 10;
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        mgr.setLinear(minValue, maxValue, nbOfClasses);
+        mgr.setColorSequenceName(NAME);
+        final List<ColorInterval> intervals = mgr.getColorIntervals();
+        assertNotNull(intervals);
+        assertEquals(nbOfClasses, intervals.size());
+        final String actual = ColorSequenceManager.getColorForValue(intervals, value);
+        final String expected = ColorSequences.SEQUENCES.get(NAME).get(nbOfClasses).get(nbOfClasses - 1);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void minEqualsMaxLinear() throws Exception {
+        final double value = 5.625;
+        final double maxValue = value;
+        final double minValue = value;
+        final int nbOfClasses = 10;
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        mgr.setLinear(minValue, maxValue, nbOfClasses);
+        mgr.setColorSequenceName(NAME);
+        final List<ColorInterval> intervals = mgr.getColorIntervals();
+        assertNotNull(intervals);
+        assertEquals(nbOfClasses, intervals.size());
+        final String actual = ColorSequenceManager.getColorForValue(intervals, value);
+        final String expected = ColorSequences.SEQUENCES.get(NAME).get(nbOfClasses).get(0);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void minEqualsMaxQuantile() throws Exception {
+        final double value = 5.625;
+        final double maxValue = value;
+        final double minValue = value;
+        final QuantileType quantile = QuantileType.CENTILES_05;
+        final List<Double> results = Arrays.asList(minValue, value, value, value, maxValue);
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        mgr.setQuantile(quantile, results);
+        mgr.setColorSequenceName(NAME);
+        final List<ColorInterval> intervals = mgr.getColorIntervals();
+        assertNotNull(intervals);
+        assertEquals(1, intervals.size());
+        final String actual = ColorSequenceManager.getColorForValue(intervals, value);
+        final String expected = ColorSequences.SEQUENCES.get(NAME).get(quantile.getNbOfClasses()).get(0);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Test #6332.
+     */
+    @Test
+    void numberOfClasses6332() {
+        final double maxValue = 3.212037037037038;
+        final double minValue = 1.2354814814814814;
+        final int nbOfClasses = 3;
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        mgr.setLinear(minValue, maxValue, nbOfClasses);
+        mgr.setColorSequenceName(NAME);
+        final List<ColorInterval> intervals = mgr.getColorIntervals();
+        assertNotNull(intervals);
+        assertEquals(nbOfClasses, intervals.size());
+    }
+
+    @Test
+    void quantileCentile05() {
+        final List<Double> results = new ArrayList<>();
+        for (double d = 0.; d < 100.; d += 1) {
+            results.add(d);
+        }
+        final QuantileType quantile = QuantileType.CENTILES_05;
+        final Double minValue = results.get(0);
+        final Double maxValue = results.get(results.size() - 1);
+        final int nbOfClasses = quantile.getNbOfClasses();
+
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        mgr.setQuantile(quantile, results);
+        mgr.setColorSequenceName(NAME);
+        final List<ColorInterval> intervals = mgr.getColorIntervals();
+        assertEquals(nbOfClasses, intervals.size());
+        final String firstColor = intervals.get(0).getColor();
+        final String secondColor = intervals.get(1).getColor();
+        final String thirdColor = intervals.get(2).getColor();
+        // < 5% : first color
+        assertEquals(firstColor, ColorSequenceManager.getColorForValue(intervals, minValue));
+        // 5% - 95% : second color
+        assertEquals(secondColor, ColorSequenceManager.getColorForValue(intervals, minValue + 6.));
+        assertEquals(secondColor, ColorSequenceManager.getColorForValue(intervals, (minValue + maxValue) / 2));
+        assertEquals(secondColor, ColorSequenceManager.getColorForValue(intervals, maxValue - 6.));
+        // > 95% : third color
+        assertEquals(thirdColor, ColorSequenceManager.getColorForValue(intervals, maxValue));
+    }
+
+    /**
+     * siclima#5890.
+     */
+    @Test
+    void quantileWith2Results() {
+        final double minValue = 5.625;
+        final double maxValue = 6.645;
+        final List<Double> results = Arrays.asList(minValue, maxValue);
+        final QuantileType quantile = QuantileType.CENTILES_05;
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        mgr.setQuantile(quantile, results);
+        mgr.setColorSequenceName(NAME);
+        final List<ColorInterval> intervals = mgr.getColorIntervals();
+        final String colorAt = ColorSequenceManager.getColorForValue(intervals, minValue);
+        assertNotNull(colorAt);
+    }
+
+    @Test
+    void valueOutOfRange() {
+        final double maxValue = 100.2;
+        final double minValue = 9.8;
+        final int nbOfClasses = 10;
+
+        final ColorSequenceManager mgr = new ColorSequenceManager();
+        mgr.setLinear(minValue, maxValue, nbOfClasses);
+        mgr.setColorSequenceName(NAME);
+        final List<ColorInterval> intervals = mgr.getColorIntervals();
+        assertNotNull(intervals);
+        assertEquals(nbOfClasses, intervals.size());
+
+        String actual, expected;
+        actual = ColorSequenceManager.getColorForValue(intervals, 0.);
+        expected = intervals.get(0).getColor();
+        assertEquals(expected, actual);
+        actual = ColorSequenceManager.getColorForValue(intervals, 1000.);
+        expected = intervals.get(nbOfClasses - 1).getColor();
+        assertEquals(expected, actual);
+    }
+}
diff --git a/www-server/pom.xml b/www-server/pom.xml
index 45b836a..573ff4b 100644
--- a/www-server/pom.xml
+++ b/www-server/pom.xml
@@ -144,12 +144,6 @@
       <version>42.6.0</version>
     </dependency>
     <!-- Tests -->
-    <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter</artifactId>
-      <version>${junit.version}</version>
-      <scope>test</scope>
-    </dependency>
     <dependency>
       <groupId>com.h2database</groupId>
       <artifactId>h2</artifactId>
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java b/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java
index 32b1e9b..96316dc 100644
--- a/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java
@@ -227,11 +227,11 @@ public class IndicatorResource implements IndicatorService {
         // If nothing said: computed value.
         if (Boolean.FALSE.equals(comparison)) {
             values.stream() //
-            .map(IndicatorResource::toFeatureWithComparedValue) //
+                    .map(IndicatorResource::toFeatureWithValue) //
             .forEach(collection::add);
         } else {
             values.stream() //
-            .map(IndicatorResource::toFeatureWithValue) //
+                    .map(IndicatorResource::toFeatureWithComparedValue) //
             .forEach(collection::add);
         }
         return collection;
diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java
index 62a66dd..97ba997 100644
--- a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java
+++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java
@@ -10,7 +10,7 @@ public final class ChoiceDTO {
     /**
      * The user wants to compare with normal.
      */
-    private Boolean comparison = false;
+    private boolean comparison = false;
 
     /**
      * ID of chosen indicator.
@@ -36,7 +36,7 @@ public final class ChoiceDTO {
      * @return true if the user wants to compare with normal.
      */
     public boolean getComparison() {
-        return Boolean.TRUE.equals(comparison);
+        return comparison;
     }
 
     /**
@@ -79,7 +79,7 @@ public final class ChoiceDTO {
     /**
      * @param value true if the user wants to compare with normal.
      */
-    public void setComparison(final Boolean value) {
+    public void setComparison(final boolean value) {
         this.comparison = value;
     }
 
-- 
GitLab