diff --git a/CHANGELOG.md b/CHANGELOG.md
index 879186b0..ebacc3d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ and this project tries to adhere to [Semantic Versioning](https://semver.org/spe
- results layer with customizable result views
### Changed
+- update test API JSON and dependent JS implementation to reflect changes made in #77
- make data in legend dynamically and add legend schema and example
- extend popup schema with key values component
- load satellite layer beneath symbol layers #43
diff --git a/digiplan/map/layers.py b/digiplan/map/layers.py
index 4c84bcaa..94f9bd8a 100644
--- a/digiplan/map/layers.py
+++ b/digiplan/map/layers.py
@@ -73,7 +73,8 @@ class RasterLayerData:
name="Ergebnisse",
name_singular="Ergebnis",
description="",
- popup_fields=["name"],
+ popup_fields=("title", "municipality", "key-values", "chart", "description", "sources"),
+ # order matters
)
]
}
diff --git a/digiplan/static/js/helper.js b/digiplan/static/js/helper.js
index 7e2b5420..f915799b 100644
--- a/digiplan/static/js/helper.js
+++ b/digiplan/static/js/helper.js
@@ -14,6 +14,13 @@ function logMessage(msg) {
}
}
+function getLanguage() {
+ // TODO: implement dynamic language determination
+ // In the future this will properly be implement.
+ // For now, this function always returns "en"
+ return "en-US";
+}
+
// TODO: there is another unmerged branch with this util function (popup.js).
// Remove duplicate entry in popup.js after merge.
async function fetchGetJson(url) {
diff --git a/digiplan/static/js/popup.js b/digiplan/static/js/popup.js
index 4138b8eb..05f9ef20 100644
--- a/digiplan/static/js/popup.js
+++ b/digiplan/static/js/popup.js
@@ -1,33 +1,207 @@
"use strict";
-function add_popup(layer_id, fields, template_id="default") {
- map.on("click", layer_id, function (e) {
- let coordinates;
- // Check if popup already exists:
- if ($('.mapboxgl-popup').length > 0) {return;}
-
- if ("lat" in e.features[0].properties) {
- // Get coordinates from lat/lon:
- coordinates = [e.features[0].properties.lat, e.features[0].properties.lon];
- } else {
- // Get coordinates from geometry:
- coordinates = e.lngLat;
+async function fetchGetJson(url) {
+ try {
+ // Default options are marked with *
+ const response = await fetch(url, {
+ method: "GET", // *GET, POST, PUT, DELETE, etc.
+ mode: "cors", // no-cors, *cors, same-origin
+ cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
+ credentials: "same-origin", // include, *same-origin, omit
+ headers: {
+ "Content-Type": "application/json",
+ }, redirect: "follow", // manual, *follow, error
+ referrerPolicy: "no-referrer", // no-referrer, *client
+ });
+ return await response.json(); // parses JSON response into native JavaScript objects
+ } catch (err) {
+ if (err instanceof Error) {
+ throw new Error(err.message);
}
- var template = $($("#" + template_id + "_popup").prop("content")).find("div");
- var clone = template.clone();
- var table = clone.find("table");
- for (var label in fields) {
- const key = fields[label];
- const value = e.features[0].properties[key];
- let row = $("
");
- row.append($("").text(label));
- row.append($(" | ").text(value));
- table.append(row);
+ throw err;
+ }
+}
+
+function createCoordinates(event) {
+ if ("lat" in event.features[0].properties) {
+ return [event.features[0].properties.lat, event.features[0].properties.lon];
+ }
+ return event.lngLat;
+}
+
+function createListByName(name, series) {
+ let list = [];
+ for (const item in series) {
+ list.push(series[item][name]);
+ }
+ return list;
+}
+
+function add_popup(layer_id, fields, template_id = "default") {
+ map.on("click", layer_id, function (event) {
+ /*
+ Check if popup already exists
+ */
+ if ($('.mapboxgl-popup').length > 0) {
+ return;
}
- new maplibregl.Popup().setLngLat(coordinates).setHTML(clone.html()).addTo(map);
+
+ /*
+ Construct Coordinates From Event
+ */
+ const coordinates = createCoordinates(event);
+
+ // TODO: construct dynamically via emitted id by event
+ const url = "/static/tests/api/popup.json??lookup=population&municipality=12lang=en";
+
+ fetchGetJson(url).then(// TODO: for now we assume response has chart. Later determine dynamically.
+ (response) => {
+ /*
+ Construct Popup From Event And Params
+ */
+ const template = document.getElementById("js-" + template_id + "_popup");
+ const clone = template.content.cloneNode(true);
+ const html = clone.getElementById("js-" + "popup");
+ for (const field in fields) {
+ if (field === "title") {
+ const titleElement = html.querySelector("#js-popup__title");
+ const {title} = response;
+ titleElement.innerHTML = title;
+ }
+ if (field === "municipality") {
+ const municipalityElement = html.querySelector("#js-popup__municipality");
+ const {municipality} = response;
+ municipalityElement.innerHTML = `(${municipality})`;
+ }
+ if (field === "key-values") {
+ const lang = getLanguage();
+ const keyValuesElement = html.querySelector("#js-popup__key-values");
+ const {
+ keyValues: {
+ unit,
+ year,
+ municipalityValue,
+ regionTitle,
+ regionValue,
+ }
+ } = response;
+ keyValuesElement.innerHTML = `
+
+ ${unit}
+ ${year}
+ ${municipalityValue.toLocaleString(lang)}
+
+
+ ${regionTitle}:
+ ${regionValue.toLocaleString(lang)}
+
+ `;
+ }
+ if (field === "description") {
+ const descriptionElement = html.querySelector("#js-popup__description");
+ const {description} = response;
+ descriptionElement.innerHTML = description;
+ }
+ if (field === "chart") {
+
+ // Chart Title
+ const {chart: {title}} = response;
+
+ // Chart
+ const chartElement = html.querySelector("#js-popup__chart");
+ const chart = echarts.init(chartElement, null, {renderer: 'svg'});
+ // TODO: use chartType in payload to construct chart dynamically. For now we assume bar chart type.
+ // TODO: In this fetch we always expect one payload item. Make failsafe.
+ const {chart: {data: {series}}} = response;
+ const xAxisData = createListByName("key", series);
+ const yAxisData = createListByName("value", series);
+ const option = {
+ title: {
+ text: title,
+ textStyle: {
+ color: '#002E50',
+ fontSize: 14,
+ fontWeight: 400,
+ lineHeight: 16
+ },
+ left: 'center'
+ },
+ animation: false,
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ }
+ },
+ grid: {
+ left: 16,
+ right: 0,
+ bottom: 32,
+ top: 48,
+ containLabel: true
+ },
+ textStyle: {
+ color: '#002E50'
+ },
+ xAxis: [{
+ type: 'category',
+ data: xAxisData,
+ axisTick: {
+ show: false
+ },
+ axisLine: {
+ show: true,
+ lineStyle: {
+ color: '#ECF2F6'
+ }
+ },
+ }],
+ yAxis: [{
+ type: 'value',
+ splitLine: {
+ show: true,
+ lineStyle: {
+ color: '#ECF2F6'
+ }
+ }
+ }],
+ series: [{
+ name: 'Direct',
+ type: 'line',
+ symbol: 'circle',
+ symbolSize: 6,
+ data: yAxisData,
+ lineStyle: {
+ color: '#002E50'
+ },
+ itemStyle: {
+ color: '#002E50'
+ }
+ }]
+ };
+ chart.setOption(option);
+ requestAnimationFrame(() => {
+ new maplibregl.Popup({
+ // https://maplibre.org/maplibre-gl-js-docs/api/markers/#popup-parameters
+ maxWidth: "280px",
+ }).setLngLat(coordinates).setHTML(html.innerHTML).addTo(map);
+ });
+ }
+ if (field === "sources") {
+ const sourcesElement = html.querySelector("#js-popup__sources");
+ let links = [];
+ for (const index in response.sources) {
+ const url = response.sources[index].url;
+ const name = response.sources[index].name;
+ links.push(`${name}`);
+ }
+ sourcesElement.innerHTML = `Quellen: ${links.join(", ")}`;
+ }
+ }
+ });
});
}
-$(document).ready(function() {
+$(document).ready(function () {
$('#js-intro-modal').modal('show');
});
diff --git a/digiplan/static/scss/app.scss b/digiplan/static/scss/app.scss
index 4c1668c7..ba8c5e79 100644
--- a/digiplan/static/scss/app.scss
+++ b/digiplan/static/scss/app.scss
@@ -14,6 +14,7 @@
@import 'components/map_basemaps_control';
@import 'components/modals';
@import 'components/offcanvas';
+@import 'components/popup';
@import 'components/tabs';
@import 'components/top-nav';
diff --git a/digiplan/static/scss/base/_mixins.scss b/digiplan/static/scss/base/_mixins.scss
index 19f82401..e7aad3b9 100644
--- a/digiplan/static/scss/base/_mixins.scss
+++ b/digiplan/static/scss/base/_mixins.scss
@@ -69,6 +69,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
+ pointer-events: none;
}
// UL
diff --git a/digiplan/static/scss/base/_variables.scss b/digiplan/static/scss/base/_variables.scss
index 58833cab..72359ed1 100644
--- a/digiplan/static/scss/base/_variables.scss
+++ b/digiplan/static/scss/base/_variables.scss
@@ -21,6 +21,7 @@ $font-size-xsmall: .75rem; // 12px
$letter-spacing-small: 0.01rem;
$letter-spacing-normal: 0.03rem;
+$letter-spacing-large: 0.06rem;
// LEFT PANEL
$panel-width-sm: 22rem;
@@ -63,5 +64,8 @@ $map-control-position-left: 1rem;
$map-control-z-index: 9;
$map-control-bg-color: rgba(255, 255, 255, 1);
+// POPUP
+$popup-width: 28rem;
+
// ICONS
$info-icon-size: 1.25rem;
diff --git a/digiplan/static/scss/components/_popup.scss b/digiplan/static/scss/components/_popup.scss
new file mode 100644
index 00000000..7b215093
--- /dev/null
+++ b/digiplan/static/scss/components/_popup.scss
@@ -0,0 +1,94 @@
+.mapboxgl-popup-content, .maplibregl-popup-content {
+ @extend .p-0;
+ width: $popup-width;
+ font-family: $font-family-sans-serif;
+
+ .popup {
+ &__header {
+ @extend .bg-light;
+ @extend .py-2;
+ @include flex-row-justify-center;
+ @include user-select-none;
+ }
+
+ &__title,
+ &__municipality {
+ @extend .fs-4;
+ @extend .fw-light;
+ @extend .mb-0;
+ letter-spacing: $letter-spacing-large;
+ }
+
+ &__municipality {
+ @extend .ps-1;
+ }
+
+ &__key-values,
+ &__chart,
+ &__description,
+ &__sources {
+ padding: $padding-normal;
+ }
+
+ &__key-values {
+ @extend .d-flex;
+ @extend .flex-row;
+ @extend .justify-content-between;
+ @extend .fs-7;
+ @extend .border-bottom;
+ @include user-select-none;
+
+ .key-values {
+ & > span {
+ @extend .d-block;
+ }
+
+ &__municipality {
+ @extend .fw-bold;
+ }
+
+ &__region {
+ color: $gray-600;
+ }
+
+ &__region-value {
+ @extend .bg-primary;
+ @extend .text-white;
+ margin-left: 4px;
+ }
+
+ &__municipality-value {
+ @extend .bg-light;
+ }
+
+ &__region-value,
+ &__municipality-value {
+ padding: 4px 8px;
+ @extend .rounded;
+ }
+ }
+ }
+
+ &__description {
+ @extend .fw-light;
+ @extend .fs-7;
+ }
+
+ &__sources {
+ @extend .border-top;
+ @extend .lh-sm;
+
+ a {
+ @extend .fw-bold;
+ }
+ }
+ }
+
+ .maplibregl-popup-close-button,
+ .mapboxgl-popup-close-button {
+ @extend .fs-3;
+ @extend .fw-light;
+ @extend .px-2;
+ @extend .py-1;
+ }
+}
diff --git a/digiplan/static/tests/api/popup.json b/digiplan/static/tests/api/popup.json
new file mode 100644
index 00000000..9d554d7f
--- /dev/null
+++ b/digiplan/static/tests/api/popup.json
@@ -0,0 +1,59 @@
+{
+ "chart": {
+ "chartType": "bar",
+ "data": {
+ "definition": {
+ "key": {
+ "lookup": "year",
+ "name": "Year",
+ "type": "integer"
+ },
+ "value": {
+ "lookup": "population",
+ "name": "Population",
+ "type": "integer"
+ }
+ },
+ "series": [
+ {
+ "key": 2022,
+ "value": 9311
+ },
+ {
+ "key": 2030,
+ "value": 9182
+ },
+ {
+ "key": 2045,
+ "value": 8903
+ }
+ ]
+ },
+ "title": "Population Forecast"
+ },
+ "description": "The population in 2022 of Z\u00f6rbig was 9,311 inhabitants. The entire ABW region had 370,190 inhabitants at that time. The following chart shows a forecast of the population development for the years 2022, 2030, and 2045.",
+ "id": 12,
+ "keyValues": {
+ "municipalityValue": 9311,
+ "regionTitle": "ABW region",
+ "regionValue": 370190,
+ "unit": "Population",
+ "year": 2022
+ },
+ "municipality": "Z\u00f6rbig",
+ "sources": [
+ {
+ "name": "Bev\u00f6lkerungz\u00e4hlung Anhalt 2021",
+ "url": "https://wam.rl-institut.de/digiplan/sources#11"
+ },
+ {
+ "name": "Bev\u00f6lkerungsprognose Anhalt 2030",
+ "url": "https://wam.rl-institut.de/digiplan/sources#12"
+ },
+ {
+ "name": "Bev\u00f6lkerungsprognose Anhalt 2050",
+ "url": "https://wam.rl-institut.de/digiplan/sources#13"
+ }
+ ],
+ "title": "Population"
+}
diff --git a/digiplan/templates/map.html b/digiplan/templates/map.html
index 2c2702dc..696acaba 100644
--- a/digiplan/templates/map.html
+++ b/digiplan/templates/map.html
@@ -9,10 +9,10 @@
{% endblock %}
{% block css %}
- {{ block.super }}
{% compress css %}
{% endcompress %}
+ {{ block.super }}
{% endblock %}
{% block content %}
@@ -358,7 +358,7 @@
{% if debug %}
{% for layer in all_layers %}
map.on("click", "{{layer.id}}", function (e) {
- console.log(e.features[0].properties.name, e.features[0]);
+ console.log("{{layer.id}}:", e.features[0].properties.name, e.features[0]);
});
{% endfor %}
setDebugMode(true);
diff --git a/digiplan/templates/popups/default.html b/digiplan/templates/popups/default.html
index 60531ec4..721d5758 100644
--- a/digiplan/templates/popups/default.html
+++ b/digiplan/templates/popups/default.html
@@ -1,9 +1,7 @@
-
- |