Skip to content
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 separate legend for statistics charts #3170

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions integreat_cms/cms/templates/statistics/_statistics_legend.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% load i18n %}
{% load static %}
<div class="p-4">
<p class="uppercase font-bold">
{% translate "Shown languages" %}
<div class="grid grid-cols-[auto_auto_auto_1fr] gap-x-4 items-center space-y-2">
{% for language in languages %}
{{ language }}
{% endfor %}
</div>
</p>
<div id="chart-language-toggles">
</div>
</div>
<div class="p-4">
<p class="uppercase font-bold">
{% translate "Accesses" %}
</p>
<div class="grid grid-cols-[auto_auto_1fr] gap-x-4 items-center space-y-2">
{% for access in accesses %}
{{ access }}
{% endfor %}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% load i18n %}
{% load static %}
<input type="checkbox"
data-chart-item="{{ name }}"
checked=""
id="{{ name|slugify }}" />
<div class="h-5 w-5 rounded-full" style="background-color: {{ color }}">
</div>
{% if language %}
<div>
<span class="fp fp-{{ language.primary_country_code }} fp-rounded "
title="{{ language.get_primary_country_code_display }}"></span>
{% if language.secondary_country_code %}
<span class="fp fp-{{ language.secondary_country_code }} fp-rounded "
title="{{ language.get_secondary_country_code_display }}"></span>
{% endif %}
</div>
{% endif %}
<label for="{{ name|slugify }}">
{{ name }}
</label>
16 changes: 11 additions & 5 deletions integreat_cms/cms/templates/statistics/statistics_overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ <h1 class="heading">
</h1>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3 2xl:grid-cols-4">
<div class="lg:col-span-2 2xl:row-span-2 2xl:col-span-3 rounded border border-solid border-blue-500 shadow-2xl bg-white">
<div class="lg:col-span-2 2xl:row-span-3 2xl:col-span-3 rounded border border-solid border-blue-500 shadow-2xl bg-white">
<div class="rounded p-4 bg-water-500">
<h3 class="heading font-bold text-black">
<i icon-name="trending-up" class="pb-1"></i> {% translate "Total accesses" %}
Expand All @@ -29,14 +29,20 @@ <h3 class="heading font-bold text-black">
<div id="chart-loading" class="px-4 hidden">
<i icon-name="loader" class="animate-spin"></i> {% translate "Loading..." %}
</div>
<div id="chart-label-help-text"
class="my-2 text-s text-gray-600 text-left hidden">
{% translate "Individual languages can be hidden by clicking on the labels." %}
</div>
<canvas id="statistics"
data-statistics-url="{% url 'statistics_visits_per_language' region_slug=request.region.slug %}"></canvas>
</div>
</div>
<div class="rounded border border-solid border-blue-500 shadow-2xl bg-white hidden"
id="chart-legend-container">
<div class="rounded p-4 bg-water-500">
<h3 class="heading font-bold text-black">
<i icon-name="settings" class="pb-1"></i> {% translate "Adjust shown data" %}
</h3>
</div>
<div id="chart-legend">
</div>
</div>
<div class="rounded border border-solid border-blue-500 shadow-2xl bg-white">
<div class="rounded p-4 bg-water-500">
<h3 class="heading font-bold text-black">
Expand Down
27 changes: 19 additions & 8 deletions integreat_cms/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8359,6 +8359,14 @@ msgstr "Trennen"
msgid "Register authenticator app"
msgstr "Authenticator-App registrieren"

#: cms/templates/statistics/_statistics_legend.html
msgid "Shown languages"
msgstr "Angezeigte Sprachen"

#: cms/templates/statistics/_statistics_legend.html
msgid "Accesses"
msgstr "Zugriffe"

#: cms/templates/statistics/_statistics_widget.html
msgid "Number of total accesses over the last 14 days."
msgstr "Anzahl der Gesamtzugriffe der letzten 14 Tage."
Expand All @@ -8373,10 +8381,8 @@ msgid "Total accesses"
msgstr "Gesamtzugriffe"

#: cms/templates/statistics/statistics_overview.html
msgid "Individual languages can be hidden by clicking on the labels."
msgstr ""
"Einzelne Sprachen können durch Anklicken der Beschriftungen ausgeblendet "
"werden."
msgid "Adjust shown data"
msgstr "Angezeigte Daten anpassen"

#: cms/templates/statistics/statistics_overview.html
msgid "Adjust time period"
Expand Down Expand Up @@ -10820,6 +10826,10 @@ msgstr "Matomo API"
msgid "Total Accesses"
msgstr "Alle Zugriffe"

#: matomo_api/matomo_api_client.py
msgid "CW"
msgstr "KW"

#: matomo_api/matomo_api_client.py
msgid "Offline Accesses"
msgstr "Offline Zugriffe"
Expand All @@ -10828,10 +10838,6 @@ msgstr "Offline Zugriffe"
msgid "WebApp Accesses"
msgstr "WebApp Zugriffe"

#: matomo_api/matomo_api_client.py
msgid "CW"
msgstr "KW"

#: nominatim_api/apps.py
msgid "Nominatim API"
msgstr "Nominatim API"
Expand Down Expand Up @@ -11013,6 +11019,11 @@ msgstr ""
#~ msgid "%s with %s"
#~ msgstr "%s mit %s"

#~ msgid "Individual languages can be hidden by clicking on the labels."
#~ msgstr ""
#~ "Einzelne Sprachen können durch Anklicken der Beschriftungen ausgeblendet "
#~ "werden."

#~ msgid "Number of online accesses"
#~ msgstr "Anzahl der Online-Zugriffe"

Expand Down
146 changes: 106 additions & 40 deletions integreat_cms/matomo_api/matomo_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import aiohttp
from django.conf import settings
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _

from ..cms.constants import language_color, matomo_periods
Expand Down Expand Up @@ -345,54 +346,24 @@ def get_visits_per_language(
# Get the separately created datasets for offline downloads
offline_downloads = datasets.pop()

language_data, language_legends = self.get_language_data(languages, datasets)
access_data, access_legends = self.get_access_data(
total_visits, webapp_downloads, offline_downloads
)

return {
# Send original labels for usage in the CSV export (convert to list because type dict_keys is not JSON-serializable)
"exportLabels": list(total_visits.keys()),
# Return the data in the ChartData format expected by ChartJs
"chartData": {
# Make labels more readable
"labels": self.simplify_date_labels(total_visits.keys(), period),
"datasets":
# The datasets for the visits by language
[
{
"label": language.translated_name,
"backgroundColor": language.language_color,
"borderColor": language.language_color,
"data": list(dataset.values()),
}
# zip aggregates two lists into tuples, e.g. zip([1,2,3], [4,5,6])=[(1,4), (2,5), (3,6)]
# In this case, it matches the languages to their respective dataset (because the datasets are ordered)
for language, dataset in zip(languages, datasets)
]
# The dataset for offline downloads
+ [
{
"label": _("Offline Accesses"),
"backgroundColor": language_color.OFFLINE_ACCESS,
"borderColor": language_color.OFFLINE_ACCESS,
"data": list(offline_downloads.values()),
}
]
# The dataset for online/web app downloads
+ [
{
"label": _("WebApp Accesses"),
"backgroundColor": language_color.WEB_APP_ACCESS,
"borderColor": language_color.WEB_APP_ACCESS,
"data": list(webapp_downloads.values()),
}
]
# The dataset for total visits
+ [
{
"label": _("Total Accesses"),
"backgroundColor": language_color.TOTAL_ACCESS,
"borderColor": language_color.TOTAL_ACCESS,
"data": list(total_visits.values()),
}
],
"datasets": language_data + access_data,
},
"legend": render_to_string(
"statistics/_statistics_legend.html",
{"languages": language_legends, "accesses": access_legends},
),
}

@staticmethod
Expand Down Expand Up @@ -461,3 +432,98 @@ def simplify_date_labels(date_labels: KeysView[str], period: str) -> list[Promis
# This means the period is "year" (convert to list because type dict_keys is not JSON-serializable)
simplified_date_labels = list(date_labels)
return simplified_date_labels

@staticmethod
def get_language_data(
languages: list[Language], datasets: list[dict]
) -> tuple[list[dict], list[str]]:
"""
Structure the datasets for languages in a chart.js-compatible format,
returning it and the custom legend entries

:param languages: The list of languages
:param datasets: The Matomo datasets
:return: The chart.js-datasets and custom legend entries
"""
data_entries = []
legend_entries = []

for language, dataset in zip(languages, datasets):
data_entries.append(
{
"label": language.translated_name,
"backgroundColor": language.language_color,
"borderColor": language.language_color,
"data": list(dataset.values()),
}
)
legend_entries.append(
render_to_string(
"statistics/_statistics_legend_item.html",
{
"name": language.translated_name,
"color": language.language_color,
"language": language,
},
)
)
return data_entries, legend_entries

@staticmethod
def get_access_data(
total_visits: dict, webapp_downloads: dict, offline_downloads: dict
) -> tuple[list[dict], list[str]]:
"""
Structure the datasets for accesses in a chart.js-compatible format,
returning it and the custom legend entries

:param total_visits: The total amount of visits
:param webapp_downloads: The amount of visits via the WebApp
:param offline_downloads: The amount of offline downloads
:return: The chart.js-datasets and custom legend entries
"""
data_entries = [
{
"label": _("Offline Accesses"),
"backgroundColor": language_color.OFFLINE_ACCESS,
"borderColor": language_color.OFFLINE_ACCESS,
"data": list(offline_downloads.values()),
},
{
"label": _("WebApp Accesses"),
"backgroundColor": language_color.WEB_APP_ACCESS,
"borderColor": language_color.WEB_APP_ACCESS,
"data": list(webapp_downloads.values()),
},
{
"label": _("Total Accesses"),
"backgroundColor": language_color.TOTAL_ACCESS,
"borderColor": language_color.TOTAL_ACCESS,
"data": list(total_visits.values()),
},
]

legend_entries = [
render_to_string(
"statistics/_statistics_legend_item.html",
{
"name": _("Offline Accesses"),
"color": language_color.OFFLINE_ACCESS,
},
),
render_to_string(
"statistics/_statistics_legend_item.html",
{
"name": _("WebApp Accesses"),
"color": language_color.WEB_APP_ACCESS,
},
),
render_to_string(
"statistics/_statistics_legend_item.html",
{
"name": _("Total Accesses"),
"color": language_color.TOTAL_ACCESS,
},
),
]
return data_entries, legend_entries
2 changes: 2 additions & 0 deletions integreat_cms/release_notes/current/unreleased/3133.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
en: Move the legend for statistics to its own box
de: Verschiebe die Legende für Statistiken in eine eigene Box
23 changes: 16 additions & 7 deletions integreat_cms/static/src/js/analytics/statistics-charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
export type AjaxResponse = {
exportLabels: Array<string>;
chartData: ChartData;
legend: string;
};

// Register all components that are being used - the others will be excluded from the final webpack build
Expand All @@ -34,7 +35,6 @@ const updateChart = async (): Promise<void> => {
const chartServerError = document.getElementById("chart-server-error");
const chartHeavyTrafficError = document.getElementById("chart-heavy-traffic-error");
const chartLoading = document.getElementById("chart-loading");
const chartLabelHelpText = document.getElementById("chart-label-help-text");

// Hide error in case it was shown before
chartNetworkError.classList.add("hidden");
Expand Down Expand Up @@ -86,8 +86,20 @@ const updateChart = async (): Promise<void> => {
chart.update();
// Save export labels
exportLabels = data.exportLabels;
// Show help text
chartLabelHelpText?.classList.remove("hidden");

const legendDiv = document.getElementById("chart-legend-container");
const legendDivInner = document.getElementById("chart-legend");
if (legendDiv && legendDivInner) {
legendDivInner.innerHTML = data.legend;
legendDiv.classList.remove("hidden");
}
const items = chart.options.plugins.legend.labels.generateLabels(chart);
items.forEach((item) => {
document.querySelector(`[data-chart-item="${item.text}"]`).addEventListener("change", () => {
chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));
chart.update();
});
});
} else if (response.status === HTTP_STATUS_BAD_REQUEST) {
// Client error - invalid form parameters supplied
const data = await response.json();
Expand Down Expand Up @@ -203,10 +215,7 @@ window.addEventListener("load", async () => {
options: {
plugins: {
legend: {
labels: {
usePointStyle: true,
pointStyleWidth: 18,
},
display: false,
},
tooltip: {
usePointStyle: true,
Expand Down
Loading