-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add/chart to popup #73
Changes from all commits
212421b
50b0d5f
fc02664
98bec1b
a09766e
8ec3351
22fef6b
8fdec99
d09e23f
64b583b
7a6a4f1
19bc74e
957fbc8
216ea1f
391ac4c
8c40b03
29acb93
3d4ce8f
864758d
e697291
bda8bf0
bc13ff6
918472d
bd53070
bffbdf4
18e3fe1
de5e478
1f0cb0e
db58b70
42ed4ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest loading a chart option style file with named options for different charts from outside. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, of course, but not in this PR - I would suggest. This version lacks a proper API and only has one fixed chart type. The code in this PR is a first step, a MacGyver Implementation, held together by a thread, a piece of chewing gum, and some Gaffa tape. I would suggest implementing your suggestion in another PR when we have more data to play with and, therefore, different chart types. As said before, I want to make smaller PRs, to move faster. What do you think? Is this OK for you? I do not insist on my points if they are not absolutely important to me, and this one is NOT absolutely important to me! |
||
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'); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,6 +69,7 @@ | |
-moz-user-select: none; | ||
-ms-user-select: none; | ||
user-select: none; | ||
pointer-events: none; | ||
} | ||
|
||
// UL | ||
|
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; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If clauses for title, municipality and description almost look the same - maybe refactor to DRY?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I also suggest postponing this to a different PR. Because as you can see in the many
TODO
comments, this code implementation will change a lot in the future. No stone will be left untouched. But for now, I would leave it like this because it makes IMO little sense to change something that will totally be changed in the future. If I keep the if-clauses, work with switch-cases or some function calls is TBD and would be determined in the future. Is that OK for you?