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