Skip to content

Commit

Permalink
Merge pull request #73 from rl-institut-private/add/chart-to-popup
Browse files Browse the repository at this point in the history
Add/chart to popup
  • Loading branch information
henhuy authored Oct 21, 2022
2 parents ff11001 + 42ed4ac commit 330b512
Show file tree
Hide file tree
Showing 12 changed files with 386 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion digiplan/map/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
]
}
Expand Down
7 changes: 7 additions & 0 deletions digiplan/static/js/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
222 changes: 198 additions & 24 deletions digiplan/static/js/popup.js
Original file line number Diff line number Diff line change
@@ -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 = $("<tr>");
row.append($("<td>").text(label));
row.append($("<td>").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 = `
<span class="key-values__municipality">
<span class="key-values__unit">${unit}</span>
<span class="key-values__year">${year}</span>
<span class="key-values__region-value">${municipalityValue.toLocaleString(lang)}</span>
</span>
<span class="key-values__region">
<span class="key-values__municipality-title">${regionTitle}</span>:
<span class="key-values__municipality-value">${regionValue.toLocaleString(lang)}</span>
</span>
`;
}
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(`<a href="${url}">${name}</a>`);
}
sourcesElement.innerHTML = `Quellen: ${links.join(", ")}`;
}
}
});
});
}

$(document).ready(function() {
$(document).ready(function () {
$('#js-intro-modal').modal('show');
});
1 change: 1 addition & 0 deletions digiplan/static/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
1 change: 1 addition & 0 deletions digiplan/static/scss/base/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
}

// UL
Expand Down
4 changes: 4 additions & 0 deletions digiplan/static/scss/base/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
94 changes: 94 additions & 0 deletions digiplan/static/scss/components/_popup.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 330b512

Please sign in to comment.