From 4846a8835704a479e171e5cbe7926d9aa026f929 Mon Sep 17 00:00:00 2001 From: Olivier Maury <Olivier.Maury@inrae.fr> Date: Fri, 23 Feb 2024 14:49:20 +0100 Subject: [PATCH] Ajouter un cartouche sur la carte. fixes #13 --- .../www/client/presenter/LayoutPresenter.java | 19 +- .../www/client/presenter/MapPresenter.java | 30 ++- .../www/client/ui/map/CanvasTitle.java | 228 ++++++++++++++++++ .../agrometinfo/www/client/util/UiUtils.java | 21 ++ .../www/client/view/LayoutView.java | 4 - .../agrometinfo/www/client/view/MapView.java | 26 +- .../agrometinfo/www/client/public/style.css | 9 +- 7 files changed, 317 insertions(+), 20 deletions(-) create mode 100644 www-client/src/main/java/fr/agrometinfo/www/client/ui/map/CanvasTitle.java create mode 100644 www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java index ed764cc..868d4e3 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java @@ -1,5 +1,6 @@ package fr.agrometinfo.www.client.presenter; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -9,6 +10,7 @@ import org.dominokit.rest.shared.request.FailedResponseBean; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.Cookies; +import fr.agrometinfo.www.client.i18n.AppConstants; import fr.agrometinfo.www.client.view.BaseView; import fr.agrometinfo.www.client.view.LayoutView; import fr.agrometinfo.www.client.view.MapView; @@ -58,6 +60,11 @@ public final class LayoutPresenter implements Presenter { void setYears(List<Integer> list); } + /** + * I18N constants. + */ + private static final AppConstants CSTS = GWT.create(AppConstants.class); + /** * Presenter for {@link MapView}. */ @@ -78,6 +85,11 @@ public final class LayoutPresenter implements Presenter { */ private final LayoutView view = new LayoutView(); + /** + * Region id : region name. + */ + private final Map<String, String> regions = new HashMap<>(); + /** * @see https://github.com/gwtproject/gwt/issues/7631#issuecomment-110876116 * @return if the application is running in (Super) DevMode. @@ -99,8 +111,10 @@ public final class LayoutPresenter implements Presenter { */ public void onChoiceChange(final ChoiceDTO choice) { IndicatorDTO chosenIndicator = null; + String periodName = ""; for (final PeriodDTO period: periods) { if (period.getCode().equals(choice.getPeriod())) { + periodName = period.getDescription(); for (final IndicatorDTO indicator : period.getIndicators()) { if (indicator.getCode().equals(choice.getIndicator())) { chosenIndicator = indicator; @@ -111,7 +125,8 @@ public final class LayoutPresenter implements Presenter { if ("0".equals(choice.getFeatureId())) { choice.setFeatureId(null); } - mapPresenter.loadValues(choice, chosenIndicator); + final String regionName = regions.getOrDefault(choice.getFeatureId(), CSTS.metropolitanFrance()); + mapPresenter.loadValues(choice, chosenIndicator, periodName, regionName); rightPanelPresenter.loadValues(choice); } @@ -134,6 +149,8 @@ public final class LayoutPresenter implements Presenter { * @param list regions with indicators */ private void setRegions(final Map<String, String> list) { + regions.clear(); + regions.putAll(list); view.setRegions(list); IndicatorServiceFactory.INSTANCE.getYears() // 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 df4bc0b..07d1ba7 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 @@ -1,5 +1,7 @@ package fr.agrometinfo.www.client.presenter; +import java.util.List; + import org.dominokit.domino.ui.utils.DominoElement; import org.dominokit.rest.JsRestfulRequestFactory; import org.dominokit.rest.shared.RestfulRequest; @@ -38,6 +40,11 @@ public final class MapPresenter implements Presenter { * @param indicator related indicator */ void setGeoJson(String geoJSON, IndicatorDTO indicator); + + /** + * @param lines the title splitted in lines + */ + void setTitle(List<String> lines); } /** @@ -59,10 +66,13 @@ public final class MapPresenter implements Presenter { /** * Load indicator values on the map. * - * @param choice user choice for the indicator values - * @param indicator related indicator for the choice + * @param choice user choice for the indicator values + * @param indicator related indicator for the choice + * @param periodName name of chosen period + * @param regionName name of chosen region */ - public void loadValues(final ChoiceDTO choice, final IndicatorDTO indicator) { + public void loadValues(final ChoiceDTO choice, final IndicatorDTO indicator, final String periodName, + final String regionName) { final JsRestfulRequestFactory factory = new JsRestfulRequestFactory(); final RestfulRequest request = factory.get(VALUES_URL); request.addQueryParam("indicator", choice.getIndicator()); @@ -74,9 +84,21 @@ public final class MapPresenter implements Presenter { } request.addQueryParam("year", String.valueOf(choice.getYear())); request.addQueryParam("comparison", String.valueOf(choice.getComparison())); - request.onSuccess(response -> view.setGeoJson(response.getBodyAsString(), indicator)).send(); + request.onSuccess(response -> { + view.setGeoJson(response.getBodyAsString(), indicator); + view.setTitle(List.of(// + periodName, // + indicator.getDescription() + "(" + indicator.getUnit() + ")", // + regionName, // + choice.getYear().toString())); + }).send(); } + /** + * When the user select a feature on the map. + * + * @param id ID of feature + */ public void onFeatureSelect(final String id) { App.getEventBus().fireEvent(FeatureSelectEvent.of(FeatureLevel.PRA, id)); } diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/ui/map/CanvasTitle.java b/www-client/src/main/java/fr/agrometinfo/www/client/ui/map/CanvasTitle.java new file mode 100644 index 0000000..b8bb994 --- /dev/null +++ b/www-client/src/main/java/fr/agrometinfo/www/client/ui/map/CanvasTitle.java @@ -0,0 +1,228 @@ +package fr.agrometinfo.www.client.ui.map; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +import com.google.gwt.canvas.dom.client.Context2d; +import com.google.gwt.canvas.dom.client.Context2d.TextAlign; +import com.google.gwt.canvas.dom.client.TextMetrics; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.i18n.client.NumberFormat; + +import fr.agrometinfo.www.client.util.UiUtils; +import fr.agrometinfo.www.client.util.color.ColorInterval; +import ol.Map; + +/** + * Draw the title box into HTML canvas of OpenLayers to allow map export. + * + * At top left. + * + * @author Olivier Maury + */ +public final class CanvasTitle extends CanvasWidget { + + /** + * Font color for title and scale. + */ + private static final String FONT_COLOR = "#000000"; + + /** + * Line height for the lines of title. + */ + private static final int LINE_HEIGHT = 12; + + /** + * Size of colored border. + */ + private static final int BORDER_SIZE = 1; + + /** + * Width of logo. + */ + private static final int LOGO_WIDTH = 150; + + /** + * Width / height computed from SVG file. + */ + private static final double LOGO_SIZE_RATIO = 2.1087; + + /** + * Total width of title box, PADDING included. + */ + private static final int MAX_WIDTH = 370; + + /** + * To format scale bounds. + */ + private static final NumberFormat NUMBER_FORMAT = NumberFormat.getFormat("0.#"); + + /** + * Keep space around logo, scale and text. + */ + private static final int PADDING = 3; + + /** + * @return total width of title box, PADDING included, limited to screen width. + */ + private static int getWidth() { + return Math.min(UiUtils.getScreenWidth(), MAX_WIDTH); + } + + /** + * Lines for the title. + */ + private List<String> title; + + /** + * Color intervals used to set background color of cells. + */ + private List<ColorInterval> colorIntervals; + + /** + * Constructor. + * + * @param map OpenLayers map. + */ + public CanvasTitle(final Map map) { + super(map); + } + + /** + * Draw the title box with background, border, logo and text. + */ + @Override + protected void draw() { + final int availableWidth = getWidth() - LOGO_WIDTH - BORDER_SIZE * 2; + final Context2d ctx = getCanvas().getContext2d(); + ctx.setFont("10px \"Helvetica Neue\", Helvetica, Arial, sans-serif"); + final List<String> lines = new ArrayList<>(); + for (final String line : title) { + if (line == null) { + continue; + } + for (final String splittedLine : splitLine(line, ctx, availableWidth)) { + lines.add(splittedLine); + } + } + this.drawBorder(lines.size()); + this.drawLogo(); + ctx.setFillStyle(FONT_COLOR); + ctx.setTextAlign(TextAlign.CENTER); + int i = 0; + final int x = PADDING + LOGO_WIDTH + (getWidth() - LOGO_WIDTH) / 2; + for (final String line : lines) { + ctx.fillText(line, x, (double) LINE_HEIGHT + LINE_HEIGHT * i); + i++; + } + this.drawScale(); + } + + /** + * Draw the box with background and border. + * + * @param nbOfLines number of lines to set the box height + */ + private void drawBorder(final int nbOfLines) { + final Context2d ctx = getCanvas().getContext2d(); + final int totalScaleHeight = 3 * LINE_HEIGHT; + final double height = Math.max(// + LINE_HEIGHT * nbOfLines + 2d * BORDER_SIZE, // + LOGO_WIDTH / LOGO_SIZE_RATIO + BORDER_SIZE + totalScaleHeight); + ctx.setFillStyle("#818181"); + ctx.fillRect(0, 0, getWidth(), height + 2 * PADDING); + ctx.setFillStyle("#ffffff"); + ctx.fillRect(0, 0, (double) getWidth() - BORDER_SIZE, height + 2 * PADDING - BORDER_SIZE); + } + + /** + * Draw the logo. + */ + private void drawLogo() { + final Context2d ctx = getCanvas().getContext2d(); + final ImageElement img = Document.get().createImageElement(); + img.setSrc("app/img/logo_etat-agrometinfo.svg"); + ctx.drawImage(img, // + 0d + BORDER_SIZE + PADDING, // + 0d + BORDER_SIZE + PADDING, // + LOGO_WIDTH, // + LOGO_WIDTH / LOGO_SIZE_RATIO // + ); + } + + /** + * Draw the horizontal scale below the logo. + */ + private void drawScale() { + final int nbOfIntervals = colorIntervals.size(); + final double logoHeight = LOGO_WIDTH / LOGO_SIZE_RATIO; + final int cellWidth = getWidth() / (nbOfIntervals + 1); + final int cellHeight = 2 * LINE_HEIGHT; + final double cellTop = logoHeight + BORDER_SIZE + PADDING; + final double cellBottom = cellTop + cellHeight; + final double labelTop = cellBottom + LINE_HEIGHT; + final Context2d ctx = getCanvas().getContext2d(); + ctx.setFont("11px \"Helvetica Neue\", Helvetica, Arial, sans-serif"); + double x = PADDING; + for (final ColorInterval value : colorIntervals) { + final String line = NUMBER_FORMAT.format(value.getMin()); + ctx.setFillStyle("#" + value.getColor()); + ctx.fillRect(x, cellTop, cellWidth, cellHeight); + ctx.setFillStyle(FONT_COLOR); + ctx.setTextAlign(TextAlign.LEFT); + ctx.fillText(line, x, labelTop); + x += cellWidth; + } + if (!colorIntervals.isEmpty()) { + final ColorInterval value = colorIntervals.get(nbOfIntervals - 1); + final String line = NUMBER_FORMAT.format(value.getMax()); + ctx.setFillStyle(FONT_COLOR); + ctx.setTextAlign(TextAlign.LEFT); + ctx.fillText(line, x, labelTop); + } + } + + /** + * @param intervals Color intervals used to set background color of cells. + * @param lines the title splitted in lines + */ + public void setTitle(final List<String> lines, final List<ColorInterval> intervals) { + this.title = lines; + this.colorIntervals = intervals; + } + + /** + * Split a line in several lines if needed according to the available width for + * the text. + * + * @param line line to split + * @param ctx canvas context + * @param availableWidth space where text must be drawn + * @return splitted lines + */ + private List<String> splitLine(final String line, final Context2d ctx, final double availableWidth) { + final List<String> lines = new ArrayList<>(); + TextMetrics measure = ctx.measureText(line); + if (measure.getWidth() <= availableWidth) { + lines.add(line); + } else { + final String[] parts = line.split(" "); + StringJoiner sj = new StringJoiner(" "); + for (final String part: parts) { + measure = ctx.measureText(sj.toString() + " " + part); + if (measure.getWidth() <= availableWidth) { + sj.add(part); + } else { + lines.add(sj.toString()); + sj = new StringJoiner(" "); + sj.add(part); + } + } + lines.add(sj.toString()); + } + + return lines; + } +} diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java new file mode 100644 index 0000000..1b35920 --- /dev/null +++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java @@ -0,0 +1,21 @@ +package fr.agrometinfo.www.client.util; + +/** + * Helpers for user interface. + * + * @author Olivier Maury + */ +public final class UiUtils { + /** + * @return screen width (px) + */ + public static native int getScreenWidth() /*-{ + return screen.width; + }-*/; + + /** + * No Constructor. + */ + private UiUtils() { + } +} 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 64352ad..9b42fee 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 @@ -25,7 +25,6 @@ import org.dominokit.domino.ui.utils.DominoElement; import org.dominokit.domino.ui.utils.ScreenMedia; import org.dominokit.rest.shared.request.FailedResponseBean; import org.jboss.elemento.Elements; -import org.jboss.elemento.EmptyContentBuilder; import org.jboss.elemento.EventCallbackFn; import org.jboss.elemento.EventType; import org.jboss.elemento.HtmlContentBuilder; @@ -37,7 +36,6 @@ import elemental2.dom.DomGlobal; import elemental2.dom.HTMLAnchorElement; import elemental2.dom.HTMLDivElement; import elemental2.dom.HTMLElement; -import elemental2.dom.HTMLImageElement; import elemental2.dom.MouseEvent; import fr.agrometinfo.www.client.i18n.AppConstants; import fr.agrometinfo.www.client.i18n.AppMessages; @@ -208,8 +206,6 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen choice.setLevel(FeatureLevel.REGION); layout = Layout.create("AgroMetInfo").show(); layout.css("app-layout"); - final EmptyContentBuilder<HTMLImageElement> logoElem = Elements.img("app/img/logo_etat-agrometinfo.svg"); - layout.setLogo(logoElem); final HTMLDivElement contentElement = div().element(); contentElement.textContent = CSTS.applicationLoading(); layout.getContentPanel().id(MapView.MAP_CONTAINER_ID); 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 1be14d5..baf67a4 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 @@ -17,6 +17,7 @@ import elemental2.dom.HTMLElement; import fr.agrometinfo.www.client.i18n.MapConstants; import fr.agrometinfo.www.client.presenter.MapPresenter; import fr.agrometinfo.www.client.ui.map.CanvasAttributions; +import fr.agrometinfo.www.client.ui.map.CanvasTitle; import fr.agrometinfo.www.client.ui.map.ControlSuppliers; import fr.agrometinfo.www.client.ui.map.TileSuppliers; import fr.agrometinfo.www.client.util.ApplicationUtils; @@ -210,9 +211,14 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma }-*/; /** - * Layer with cells. + * Title box. */ - private Vector vectorLayer; + private CanvasTitle canvasTitle; + + /** + * Color intervals used to set background color of cells. + */ + private List<ColorInterval> colorIntervals; /** * map. @@ -225,14 +231,14 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma private MapPresenter presenter; /** - * OpenLayers view. + * Layer with cells. */ - private View view; + private Vector vectorLayer; /** - * Color intervals used to set background color of cells. + * OpenLayers view. */ - private List<ColorInterval> colorIntervals; + private View view; /** * Constructor. @@ -323,6 +329,9 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma map.render(); new CanvasAttributions(map); + canvasTitle = new CanvasTitle(map); + canvasTitle.setTitle(List.of(), List.of()); + // add some interactions removeContextMenuRightClick(); addClickInteractions(); @@ -402,4 +411,9 @@ public final class MapView extends HtmlContentBuilder<HTMLElement> implements Ma presenter = p; } + @Override + public void setTitle(final List<String> lines) { + canvasTitle.setTitle(lines, colorIntervals); + } + } diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/public/style.css b/www-client/src/main/resources/fr/agrometinfo/www/client/public/style.css index 8387db5..fd5987e 100644 --- a/www-client/src/main/resources/fr/agrometinfo/www/client/public/style.css +++ b/www-client/src/main/resources/fr/agrometinfo/www/client/public/style.css @@ -89,6 +89,9 @@ select { .agrometinfo-navbar .navbar-header .menu-toggle .bars { line-height: 15px; } +.agrometinfo-leftsidebar { + max-width: 100%; +} .agrometinfo-leftsidebar.sidebar, .agrometinfo-rightsidebar.right-sidebar { height: calc(100vh - var(--logo-height)); @@ -97,6 +100,7 @@ select { .agrometinfo-rightsidebar.right-sidebar { width: var(--rightsidebar-width); padding: var(--rightsidebar-padding); + padding-bottom: 2em; } .agrometinfo-rightsidebar.right-sidebar.slide-out-right { right: calc(-1 * var(--rightsidebar-width)); @@ -181,11 +185,6 @@ div.idp { :root { --logo-height: 50px; } -@media screen and (max-width: 450px) { - .navbar-brand { - display: none; - } -} @media screen and (max-width: 700px) { :root { --rightsidebar-padding: 1em; -- GitLab