You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2026/06/15 19:36:14 UTC

(streampipes) branch dev updated: feat: Extend visualization options (#4561)

This is an automated email from the ASF dual-hosted git repository.

dominikriemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new a04db10d02 feat: Extend visualization options (#4561)
a04db10d02 is described below

commit a04db10d029db220a9d4679d0078376292088fbe
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Mon Jun 15 21:36:09 2026 +0200

    feat: Extend visualization options (#4561)
---
 ui/deployment/i18n/de.json                         |  29 +-
 ui/deployment/i18n/en.json                         |  27 +-
 ui/deployment/i18n/pl.json                         |  29 +-
 .../model/datalake/data-lake-query-config.model.ts |   1 +
 .../field-order-config.component.html              |  90 +++++
 .../field-order-config.component.scss}             |  49 +--
 .../field-order-config.component.ts                | 105 ++++++
 .../time-series-item-config.component.html         |   5 +-
 .../time-series-item-config.component.ts           |  26 +-
 .../config/gauge-widget-config.component.html      |   7 +-
 .../gauge/config/gauge-widget-config.component.ts  |  50 +++
 .../charts/gauge/gauge-renderer.service.ts         |  17 +-
 .../progress-bar-appearance-config.component.html  |  46 +++
 .../progress-bar-appearance-config.component.scss  |  34 ++
 .../progress-bar-appearance-config.component.ts    |  88 +++++
 .../progress-bar-widget-config.component.html      | 159 +++++++++
 .../config/progress-bar-widget-config.component.ts | 154 +++++++++
 .../model/progress-bar-widget.model.ts             |  54 +++
 .../progress-bar-widget.component.html             | 112 ++++++
 .../progress-bar-widget.component.scss             | 107 ++++++
 .../progress-bar/progress-bar-widget.component.ts  | 376 +++++++++++++++++++++
 .../config/table-widget-config.component.html      |  88 +----
 .../config/table-widget-config.component.scss      |  53 ---
 .../table/config/table-widget-config.component.ts  |  69 +---
 .../charts/table/table-widget.component.ts         |  58 +++-
 .../sp-timeseries-renderer.service.ts              |  13 +-
 .../value-card-appearance-config.component.html    |  50 +++
 .../value-card-appearance-config.component.ts      |  84 +++++
 .../config/value-card-widget-config.component.html |  86 +++++
 .../config/value-card-widget-config.component.ts   | 133 ++++++++
 .../value-card/model/value-card-widget.model.ts    |  45 +++
 .../value-card/value-card-widget.component.html    |  91 +++++
 .../value-card/value-card-widget.component.scss    | 118 +++++++
 .../value-card/value-card-widget.component.ts      | 371 ++++++++++++++++++++
 .../registry/chart-registry.service.ts             |  30 ++
 .../chart-shared/services/result-label.service.ts  |  70 ++++
 .../chart-shared/services/widget-render.utils.ts   |  79 +++++
 .../chart-data-settings.component.html             |   5 +
 .../data-settings/chart-data-settings.component.ts |   3 +
 .../result-label-configuration.component.html      |  49 +++
 .../result-label-configuration.component.scss      |  17 +
 .../result-label-configuration.component.ts        |  93 +++++
 42 files changed, 2902 insertions(+), 268 deletions(-)

diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index e0aada136d..56d3b9f2c0 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -146,10 +146,12 @@
   "Backend Version": "Backend-Version",
   "Background": "Hintergrund",
   "Bar": "Balken",
+  "Bar settings": "Balkeneinstellungen",
   "Basic": "Allgemein",
   "Basic Settings": "Basiseinstellungen",
   "Basic settings": "Grundeinstellungen",
   "Basics": "Allgemein",
+  "Behavior": "Verhalten",
   "Behaviour in case of missing values": "Verhalten im Falle von fehlenden Daten",
   "Boolean": "Boolean",
   "Boolean Value": "Boolescher Wert",
@@ -161,6 +163,7 @@
   "CSV configuration": "CSV-Konfiguration",
   "Cancel": "Abbrechen",
   "Car": "Auto",
+  "Card settings": "Karteneinstellungen",
   "Caution when changing data types": "Vorsicht bei Änderung des Datentyps",
   "Caution when loading total count": "Vorsicht beim Laden der Gesamtanzahl",
   "Certificate Details": "Zertifikat-Details",
@@ -186,6 +189,7 @@
   "Choose existing file": "Vorhandene Datei auswählen",
   "Choose target dataset": "Ziel-Dataset auswählen",
   "Choose template": "Vorlage auswählen",
+  "Clamp to 0-100%": "Auf 0-100 % begrenzen",
   "Clean code": "Code aufräumen",
   "Clear": "Zurücksetzen",
   "Clear & use simple filters": "Löschen und einfache Filter verwenden",
@@ -209,7 +213,6 @@
   "Color used above the high threshold.": "Farbe, die oberhalb des hohen Schwellenwerts verwendet wird.",
   "Color used between low and high thresholds.": "Farbe, die zwischen niedrigem und hohem Schwellenwert verwendet wird.",
   "Color used from min up to the low threshold.": "Farbe, die von Min bis zum niedrigen Schwellenwert verwendet wird.",
-  "Column Order": "Spaltenreihenfolge",
   "Compatible Elements": "Kompatible Elemente",
   "Configuration": "Konfiguration",
   "Configuration required": "Konfiguration erforderlich",
@@ -254,11 +257,13 @@
   "Creating adapter {{adapterName}}": "Adapter erstellen {{adapterName}}",
   "Creating pipeline to persist data stream": "Erstellen einer Pipeline zum Speichern der Daten",
   "Critical datatype changes": "Kritische Datentypänderungen",
+  "Current / Target": "Aktuell / Ziel",
   "Current Warning Range: ": "Aktueller Warnbereich: ",
   "Current day": "Aktueller Tag",
   "Current hour": "Aktuelle Stunde",
   "Current month": "Aktueller Monat",
   "Current password": "Aktuelles Passwort",
+  "Current value field": "Feld für aktuellen Wert",
   "Current week": "Aktuelle Woche",
   "Current year": "Aktuelles Jahr",
   "Currently configured query": "Aktuell konfigurierte Anfrage",
@@ -344,7 +349,10 @@
   "Discard changes": "Änderungen verwerfen",
   "Display Name": "Anzeigename",
   "Display an image": "Ein Bild anzeigen",
+  "Display mode": "Anzeigemodus",
   "Display points on an x/y plane": "Punkte in einer x/y-Ebene anzeigen",
+  "Display the latest values of multiple selected fields in a single card": "Die neuesten Werte mehrerer ausgewählter Felder in einer einzelnen Karte anzeigen",
+  "Display the progress of a numeric value against a target": "Den Fortschritt eines numerischen Werts im Verhältnis zu einem Ziel anzeigen",
   "Do you really want to delete adapter ": "Wollen Sie den Adapter wirklich löschen",
   "Do you really want to delete pipeline": "Wollen Sie die Pipeline wirklich löschen?",
   "Do you really want to delete the dataset {{index}}?": "Möchten Sie den Datensatz {{index}} wirklich löschen?",
@@ -459,6 +467,7 @@
   "First": "Erster",
   "First row contains headers": "Erste Zeile enthält Überschriften",
   "First row index to append data": "Erster Zeilenindex an denen Daten angehängt werden sollen",
+  "Fixed value": "Fester Wert",
   "Focus map on last event": "Karte auf letztes Ereignis fokussieren",
   "Font Style": "Schriftstil",
   "For tile server, use placeholders for x, y and z coordinates": "Für Tile-Server Platzhalter für x-, y- und z-Koordinaten verwenden",
@@ -552,6 +561,7 @@
   "Invalid URI format.": "Ungültiges URI-Format.",
   "Invalid configuration for": "Ungültige Konfiguration für",
   "Invalid pipeline: Modify to fix problems": "Ungültige Pipeline: Ändern, um Probleme zu beheben",
+  "Invert progress semantics": "Fortschrittslogik umkehren",
   "Issuer": "Aussteller",
   "Items per page": "Einträge pro Seite",
   "JWT Signature": "JWT-Signature",
@@ -571,6 +581,7 @@
   "Label Content": "Beschriftungsinhalt",
   "Label Line": "Beschriftungslinie",
   "Label Position": "Beschriftungsposition",
+  "Label font size": "Schriftgröße der Bezeichnung",
   "Label shown below the current gauge value.": "Beschriftung, die unter dem aktuellen Tacho-Wert angezeigt wird.",
   "Labels": "Labels",
   "Labels & custom fields": "Label und benutzerdefinierte Felder",
@@ -713,7 +724,6 @@
   "No datasets match your search.": "Keine Datensätze entsprechen deiner Suche.",
   "No entries available.": "Keine Einträge vorhanden.",
   "No export providers found": "Keine Exportanbieter gefunden",
-  "No fields selected.": "Keine Felder ausgewählt.",
   "No groups configured": "Keine Gruppen konfiguriert",
   "No labels available - Click 'Manage Labels'": "Keine Labels verfügbar - Klicken Sie auf 'Labels verwalten'",
   "No linked resources": "Keine verknüpften Ressourcen",
@@ -760,6 +770,8 @@
   "Passwords does not match.": "Die Passwörter stimmen nicht überein.",
   "Path": "Pfad",
   "Percent": "Prozent",
+  "Percent + Current / Target": "Prozent + Aktuell / Ziel",
+  "Percent only": "Nur Prozent",
   "Permissions": "Berechtigungen",
   "Persist events": "Daten speichern",
   "Pie": "Kreisdiagramm",
@@ -803,6 +815,8 @@
   "Probable cause": "Wahrscheinliche Ursache",
   "Problems reading the following nodes": "Probleme beim Lesen der folgenden Knoten",
   "Profile": "Profil",
+  "Progress Bar": "Fortschrittsbalken",
+  "Progress color": "Fortschrittsfarbe",
   "Protocol": "Protokoll",
   "Provider Settings": "Anbieter-Einstellungen",
   "Provider Type": "Anbieter Typ",
@@ -906,8 +920,8 @@
   "Schema fields": "Schemafelder",
   "Script": "Skript",
   "Search": "Suche",
-  "Search datasets": "Datensätze suchen",
   "Search charts": "Diagramme suchen",
+  "Search datasets": "Datensätze suchen",
   "Searching for available extensions, please wait...": "Nach verfügbaren Extensions suchen, bitte warten...",
   "Second": "Sekunde",
   "Secret Key": "Secret Key",
@@ -971,9 +985,11 @@
   "Show documentation link in user menu": "Link zur Dokumentation im Benutzermenü anzeigen",
   "Show documentation link on login page": "Link zur Dokumentation auf Anmeldeseite anzeigen",
   "Show input fields": "Eingabefelder anzeigen",
+  "Show latest time above card": "Neueste Zeit über der Karte anzeigen",
   "Show legend": "Legende anzeigen",
   "Show only recommended settings": "Nur empfohlene Einstellungen anzeigen",
   "Show pipeline configuration as code": "Pipeline-Konfiguration als Code anzeigen",
+  "Show progress label": "Fortschrittsbeschriftung anzeigen",
   "Show raw data from your data source.": "Zeigen Sie Rohdaten aus Ihrer Datenquelle an.",
   "Show single data entry.": "Einzelne Dateneinträge anzeigen.",
   "Show subassets": "Teilassets anzeigen",
@@ -1044,6 +1060,9 @@
   "System Info": "Systeminformationen",
   "Table": "Tabelle",
   "Tags": "Tags",
+  "Target field": "Zielfeld",
+  "Target source": "Zielquelle",
+  "Target value": "Zielwert",
   "Template": "Vorlage",
   "Template description": "Beschreibung der Vorlage",
   "Template name": "Name der Vorlage",
@@ -1124,6 +1143,7 @@
   "Topics": "Themen",
   "Total Memory": "Gesamtspeicher",
   "Trace": "Spur",
+  "Track color": "Hintergrundfarbe",
   "Traffic Light": "Ampel",
   "Transformation": "Transformation",
   "Transport": "Transport",
@@ -1195,10 +1215,13 @@
   "Uses proxy": "Verwendet Proxy",
   "Validation Error": "Validierungsfehler",
   "Value": "Wert",
+  "Value Card": "Wertkarte",
   "Value Distribution Heatmap": "Wertverteilung-Heatmap",
   "Value Highlighting": "Wert-Hervorhebung",
+  "Value font size": "Schriftgröße des Werts",
   "Value where the low color range ends.": "Wert, bei dem der niedrige Farbbereich endet.",
   "Value where the medium color range ends and high begins.": "Wert, bei dem der mittlere Farbbereich endet und der hohe beginnt.",
+  "Values": "Werte",
   "Vector": "Vektor",
   "Versions": "Versionen",
   "View API Docs": "API-Doku anzeigen",
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index c81b30c8a5..e58a1a807e 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -146,10 +146,12 @@
   "Backend Version": null,
   "Background": null,
   "Bar": null,
+  "Bar settings": null,
   "Basic": null,
   "Basic Settings": null,
   "Basic settings": null,
   "Basics": null,
+  "Behavior": null,
   "Behaviour in case of missing values": null,
   "Boolean": null,
   "Boolean Value": null,
@@ -161,6 +163,7 @@
   "CSV configuration": null,
   "Cancel": null,
   "Car": null,
+  "Card settings": null,
   "Caution when changing data types": null,
   "Caution when loading total count": null,
   "Certificate Details": null,
@@ -186,6 +189,7 @@
   "Choose existing file": null,
   "Choose target dataset": null,
   "Choose template": null,
+  "Clamp to 0-100%": null,
   "Clean code": null,
   "Clear": null,
   "Clear & use simple filters": null,
@@ -209,7 +213,6 @@
   "Color used above the high threshold.": null,
   "Color used between low and high thresholds.": null,
   "Color used from min up to the low threshold.": null,
-  "Column Order": null,
   "Compatible Elements": null,
   "Configuration": null,
   "Configuration required": null,
@@ -254,11 +257,13 @@
   "Creating adapter {{adapterName}}": "Creating adapter {{adapterName}}",
   "Creating pipeline to persist data stream": null,
   "Critical datatype changes": null,
+  "Current / Target": null,
   "Current Warning Range: ": null,
   "Current day": null,
   "Current hour": null,
   "Current month": null,
   "Current password": null,
+  "Current value field": null,
   "Current week": null,
   "Current year": null,
   "Currently configured query": null,
@@ -344,7 +349,10 @@
   "Discard changes": null,
   "Display Name": null,
   "Display an image": null,
+  "Display mode": null,
   "Display points on an x/y plane": null,
+  "Display the latest values of multiple selected fields in a single card": null,
+  "Display the progress of a numeric value against a target": null,
   "Do you really want to delete adapter ": null,
   "Do you really want to delete pipeline": null,
   "Do you really want to delete the dataset {{index}}?": "Do you really want to delete the dataset {{index}}?",
@@ -459,6 +467,7 @@
   "First": null,
   "First row contains headers": null,
   "First row index to append data": null,
+  "Fixed value": null,
   "Focus map on last event": null,
   "Font Style": null,
   "For tile server, use placeholders for x, y and z coordinates": null,
@@ -552,6 +561,7 @@
   "Invalid URI format.": null,
   "Invalid configuration for": null,
   "Invalid pipeline: Modify to fix problems": null,
+  "Invert progress semantics": null,
   "Issuer": null,
   "Items per page": null,
   "JWT Signature": null,
@@ -571,6 +581,7 @@
   "Label Content": null,
   "Label Line": null,
   "Label Position": null,
+  "Label font size": null,
   "Label shown below the current gauge value.": null,
   "Labels": null,
   "Labels & custom fields": null,
@@ -713,7 +724,6 @@
   "No datasets match your search.": null,
   "No entries available.": null,
   "No export providers found": null,
-  "No fields selected.": null,
   "No groups configured": null,
   "No labels available - Click 'Manage Labels'": null,
   "No linked resources": null,
@@ -760,6 +770,8 @@
   "Passwords does not match.": null,
   "Path": null,
   "Percent": null,
+  "Percent + Current / Target": null,
+  "Percent only": null,
   "Permissions": null,
   "Persist events": null,
   "Pie": null,
@@ -803,6 +815,8 @@
   "Probable cause": null,
   "Problems reading the following nodes": null,
   "Profile": null,
+  "Progress Bar": null,
+  "Progress color": null,
   "Protocol": null,
   "Provider Settings": null,
   "Provider Type": null,
@@ -971,9 +985,11 @@
   "Show documentation link in user menu": null,
   "Show documentation link on login page": null,
   "Show input fields": null,
+  "Show latest time above card": null,
   "Show legend": null,
   "Show only recommended settings": null,
   "Show pipeline configuration as code": null,
+  "Show progress label": null,
   "Show raw data from your data source.": null,
   "Show single data entry.": null,
   "Show subassets": null,
@@ -1044,6 +1060,9 @@
   "System Info": null,
   "Table": null,
   "Tags": null,
+  "Target field": null,
+  "Target source": null,
+  "Target value": null,
   "Template": null,
   "Template description": null,
   "Template name": null,
@@ -1124,6 +1143,7 @@
   "Topics": null,
   "Total Memory": null,
   "Trace": null,
+  "Track color": null,
   "Traffic Light": null,
   "Transformation": null,
   "Transport": null,
@@ -1195,10 +1215,13 @@
   "Uses proxy": null,
   "Validation Error": null,
   "Value": null,
+  "Value Card": null,
   "Value Distribution Heatmap": null,
   "Value Highlighting": null,
+  "Value font size": null,
   "Value where the low color range ends.": null,
   "Value where the medium color range ends and high begins.": null,
+  "Values": null,
   "Vector": null,
   "Versions": null,
   "View API Docs": null,
diff --git a/ui/deployment/i18n/pl.json b/ui/deployment/i18n/pl.json
index 506b51c71c..b9062bcd9f 100644
--- a/ui/deployment/i18n/pl.json
+++ b/ui/deployment/i18n/pl.json
@@ -146,10 +146,12 @@
   "Backend Version": "Wersja backendu",
   "Background": "Tło",
   "Bar": "Słupkowy",
+  "Bar settings": "Ustawienia paska",
   "Basic": "Ogólne",
   "Basic Settings": "Ustawienia podstawowe",
   "Basic settings": "Ustawienia podstawowe",
   "Basics": "Podstawy",
+  "Behavior": "Zachowanie",
   "Behaviour in case of missing values": "Zachowanie w przypadku brakujących wartości",
   "Boolean": "Logiczny",
   "Boolean Value": "Wartość logiczna",
@@ -161,6 +163,7 @@
   "CSV configuration": "Konfiguracja CSV",
   "Cancel": "Anuluj",
   "Car": "Samochód",
+  "Card settings": "Ustawienia karty",
   "Caution when changing data types": "Uwaga przy zmianie typów danych",
   "Caution when loading total count": "Uwaga przy wczytywaniu łącznej liczby",
   "Certificate Details": "Szczegóły certyfikatu",
@@ -186,6 +189,7 @@
   "Choose existing file": "Wybierz istniejący plik",
   "Choose target dataset": "Wybierz docelowy zbiór danych",
   "Choose template": "Wybierz szablon",
+  "Clamp to 0-100%": "Ogranicz do 0-100%",
   "Clean code": "Wyczyść kod",
   "Clear": "Wyczyść",
   "Clear & use simple filters": "Wyczyść i użyj prostych filtrów",
@@ -209,7 +213,6 @@
   "Color used above the high threshold.": "Kolor używany powyżej wysokiego progu.",
   "Color used between low and high thresholds.": "Kolor używany między niskim a wysokim progiem.",
   "Color used from min up to the low threshold.": "Kolor używany od minimum do niskiego progu.",
-  "Column Order": "Kolejność kolumn",
   "Compatible Elements": "Kompatybilne elementy",
   "Configuration": "Konfiguracja",
   "Configuration required": "Wymagana konfiguracja",
@@ -254,11 +257,13 @@
   "Creating adapter {{adapterName}}": "Tworzenie adaptera {{adapterName}}",
   "Creating pipeline to persist data stream": "Tworzenie strumienia do utrwalenia strumienia danych",
   "Critical datatype changes": "Krytyczne zmiany typów danych",
+  "Current / Target": "Bieżąca / Docelowa",
   "Current Warning Range: ": "Bieżący zakres ostrzegawczy: ",
   "Current day": "Bieżący dzień",
   "Current hour": "Bieżąca godzina",
   "Current month": "Bieżący miesiąc",
   "Current password": "Obecne hasło",
+  "Current value field": "Pole bieżącej wartości",
   "Current week": "Bieżący tydzień",
   "Current year": "Bieżący rok",
   "Currently configured query": "Aktualnie skonfigurowane zapytanie",
@@ -344,7 +349,10 @@
   "Discard changes": "Odrzuć zmiany",
   "Display Name": "Nazwa wyświetlana",
   "Display an image": "Wyświetl obraz",
+  "Display mode": "Tryb wyświetlania",
   "Display points on an x/y plane": "Wyświetl punkty na płaszczyźnie x/y",
+  "Display the latest values of multiple selected fields in a single card": "Wyświetl najnowsze wartości wielu wybranych pól na jednej karcie",
+  "Display the progress of a numeric value against a target": "Wyświetl postęp wartości liczbowej względem celu",
   "Do you really want to delete adapter ": "Czy na pewno chcesz usunąć adapter ",
   "Do you really want to delete pipeline": "Czy na pewno chcesz usunąć strumień?",
   "Do you really want to delete the dataset {{index}}?": "Czy na pewno chcesz usunąć zbiór danych {{index}}?",
@@ -459,6 +467,7 @@
   "First": "Pierwszy",
   "First row contains headers": "Pierwszy wiersz zawiera nagłówki",
   "First row index to append data": "Indeks pierwszego wiersza, do którego dodać dane",
+  "Fixed value": "Wartość stała",
   "Focus map on last event": "Skup mapę na ostatnim zdarzeniu",
   "Font Style": "Styl czcionki",
   "For tile server, use placeholders for x, y and z coordinates": "W przypadku serwera kafelków(serwer map cyfrowych) użyj placeholderów dla współrzędnych x, y i z",
@@ -552,6 +561,7 @@
   "Invalid URI format.": "Nieprawidłowy format URI.",
   "Invalid configuration for": "Nieprawidłowa konfiguracja dla",
   "Invalid pipeline: Modify to fix problems": "Nieprawidłowy strumień: zmodyfikuj, aby naprawić problemy",
+  "Invert progress semantics": "Odwróć logikę postępu",
   "Issuer": "Wystawca",
   "Items per page": "Elementów na stronę",
   "JWT Signature": "Podpis JWT",
@@ -571,6 +581,7 @@
   "Label Content": "Zawartość etykiety",
   "Label Line": "Linia etykiety",
   "Label Position": "Pozycja etykiety",
+  "Label font size": "Rozmiar czcionki etykiety",
   "Label shown below the current gauge value.": "Etykieta wyświetlana poniżej bieżącej wartości wskaźnika.",
   "Labels": "Etykiety",
   "Labels & custom fields": "Etykiety i pola niestandardowe",
@@ -713,7 +724,6 @@
   "No datasets match your search.": "Żadne zbiory danych nie pasują do wyszukiwania.",
   "No entries available.": "Brak dostępnych wpisów.",
   "No export providers found": "Nie znaleziono exportera",
-  "No fields selected.": "Nie wybrano żadnych pól.",
   "No groups configured": "Nie skonfigurowano grup",
   "No labels available - Click 'Manage Labels'": "Brak dostępnych etykiet - kliknij 'Zarządzaj etykietami'",
   "No linked resources": "Brak powiązanych zasobów",
@@ -760,6 +770,8 @@
   "Passwords does not match.": "Hasła nie są zgodne.",
   "Path": "Ścieżka",
   "Percent": "Procent",
+  "Percent + Current / Target": "Procent + Bieżąca / Docelowa",
+  "Percent only": "Tylko procent",
   "Permissions": "Uprawnienia",
   "Persist events": "Trwałe przechowywanie zdarzeń",
   "Pie": "Wykres kołowy",
@@ -803,6 +815,8 @@
   "Probable cause": "Prawdopodobna przyczyna",
   "Problems reading the following nodes": "Problemy z odczytem następujących węzłów",
   "Profile": "Profil",
+  "Progress Bar": "Pasek postępu",
+  "Progress color": "Kolor postępu",
   "Protocol": "Protokół",
   "Provider Settings": "Ustawienia dostawcy",
   "Provider Type": "Typ dostawcy",
@@ -906,8 +920,8 @@
   "Schema fields": "Pola schematu",
   "Script": "Skrypt",
   "Search": "Szukaj",
-  "Search datasets": "Szukaj zbiorów danych",
   "Search charts": "Szukaj wykresów",
+  "Search datasets": "Szukaj zbiorów danych",
   "Searching for available extensions, please wait...": "Wyszukiwanie dostępnych rozszerzeń, proszę czekać...",
   "Second": "Sekunda",
   "Secret Key": "Klucz tajny",
@@ -971,9 +985,11 @@
   "Show documentation link in user menu": "Pokaż link do dokumentacji w menu użytkownika",
   "Show documentation link on login page": "Pokaż link do dokumentacji na stronie logowania",
   "Show input fields": "Pokaż pola wejściowe",
+  "Show latest time above card": "Pokaż najnowszy czas nad kartą",
   "Show legend": "Pokaż legendę",
   "Show only recommended settings": "Pokaż tylko zalecane ustawienia",
   "Show pipeline configuration as code": "Pokaż konfigurację strumienia jako kod",
+  "Show progress label": "Pokaż etykietę postępu",
   "Show raw data from your data source.": "Pokaż surowe dane ze źródła.",
   "Show single data entry.": "Pokaż pojedynczy wpis danych.",
   "Show subassets": "Pokaż podzasoby",
@@ -1044,6 +1060,9 @@
   "System Info": "Informacje o systemie",
   "Table": "Tabela",
   "Tags": "Tagi",
+  "Target field": "Pole docelowe",
+  "Target source": "Źródło celu",
+  "Target value": "Wartość docelowa",
   "Template": "Szablon",
   "Template description": "Opis szablonu",
   "Template name": "Nazwa szablonu",
@@ -1124,6 +1143,7 @@
   "Topics": "Tematy",
   "Total Memory": "Calkowita pamiec",
   "Trace": "Ślad",
+  "Track color": "Kolor tła paska",
   "Traffic Light": "Sygnalizator",
   "Transformation": "Transformacja",
   "Transport": "Transport",
@@ -1195,10 +1215,13 @@
   "Uses proxy": "Używa proxy",
   "Validation Error": "Błąd walidacji",
   "Value": "Wartość",
+  "Value Card": "Karta wartości",
   "Value Distribution Heatmap": "Mapa cieplna rozkładu wartości",
   "Value Highlighting": "Wyróżnianie wartości",
+  "Value font size": "Rozmiar czcionki wartości",
   "Value where the low color range ends.": "Wartość, przy której kończy się niski zakres kolorów.",
   "Value where the medium color range ends and high begins.": "Wartość, przy której kończy się średni zakres kolorów, a zaczyna wysoki.",
+  "Values": "Wartości",
   "Vector": "Wektor",
   "Versions": "Wersje",
   "View API Docs": "Zobacz dokumentację API",
diff --git a/ui/projects/streampipes/platform-services/src/lib/model/datalake/data-lake-query-config.model.ts b/ui/projects/streampipes/platform-services/src/lib/model/datalake/data-lake-query-config.model.ts
index 60a3957286..fa3f72f93e 100644
--- a/ui/projects/streampipes/platform-services/src/lib/model/datalake/data-lake-query-config.model.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/model/datalake/data-lake-query-config.model.ts
@@ -69,6 +69,7 @@ export interface QueryConfig {
     selectedFilters: SelectedFilter[];
     filterExpression?: FilterExpressionGroup;
     fields?: FieldConfig[];
+    resultLabelOverrides?: Record<string, string>;
     groupBy?: FieldConfig[];
     limit?: number;
     page?: number;
diff --git a/ui/src/app/chart-shared/components/chart-config/field-order-config/field-order-config.component.html b/ui/src/app/chart-shared/components/chart-config/field-order-config/field-order-config.component.html
new file mode 100644
index 0000000000..561fe4b053
--- /dev/null
+++ b/ui/src/app/chart-shared/components/chart-config/field-order-config/field-order-config.component.html
@@ -0,0 +1,90 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<sp-split-section [level]="3" [title]="sectionTitle | translate">
+    <div
+        fxLayout="column"
+        class="selected-field-list"
+        cdkDropList
+        [cdkDropListData]="selectedFields"
+        (cdkDropListDropped)="dropSelectedField($event)"
+    >
+        @for (field of selectedFields; track fieldKey(field); let i = $index) {
+            <div
+                class="selected-field-row"
+                fxLayout="row"
+                fxLayoutAlign="space-between center"
+                cdkDrag
+            >
+                <div
+                    class="selected-field-main"
+                    fxLayout="row"
+                    fxLayoutAlign="start center"
+                >
+                    <button
+                        mat-icon-button
+                        type="button"
+                        class="selected-field-drag-handle"
+                        cdkDragHandle
+                        [attr.aria-label]="
+                            'Reorder ' + field.runtimeName | translate
+                        "
+                    >
+                        <mat-icon>drag_indicator</mat-icon>
+                    </button>
+                    <div class="selected-field-label">
+                        {{ selectedFieldLabel(field) }}
+                    </div>
+                </div>
+                <div
+                    class="selected-field-actions"
+                    fxLayout="row"
+                    fxLayoutAlign="end center"
+                >
+                    <button
+                        mat-icon-button
+                        type="button"
+                        [disabled]="!canMoveSelectedFieldUp(i)"
+                        [attr.aria-label]="
+                            'Move ' + field.runtimeName + ' up' | translate
+                        "
+                        (click)="moveSelectedField(i, -1)"
+                    >
+                        <mat-icon>arrow_upward</mat-icon>
+                    </button>
+                    <button
+                        mat-icon-button
+                        type="button"
+                        [disabled]="!canMoveSelectedFieldDown(i)"
+                        [attr.aria-label]="
+                            'Move ' + field.runtimeName + ' down' | translate
+                        "
+                        (click)="moveSelectedField(i, 1)"
+                    >
+                        <mat-icon>arrow_downward</mat-icon>
+                    </button>
+                </div>
+            </div>
+        } @empty {
+            <sp-alert-banner
+                type="info"
+                [title]="emptyMessage | translate"
+            ></sp-alert-banner>
+        }
+    </div>
+</sp-split-section>
diff --git a/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.scss b/ui/src/app/chart-shared/components/chart-config/field-order-config/field-order-config.component.scss
similarity index 65%
copy from ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.scss
copy to ui/src/app/chart-shared/components/chart-config/field-order-config/field-order-config.component.scss
index e926be0642..94f2bc6b41 100644
--- a/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.scss
+++ b/ui/src/app/chart-shared/components/chart-config/field-order-config/field-order-config.component.scss
@@ -16,11 +16,11 @@
  *
  */
 
-.selected-column-list {
+.selected-field-list {
     gap: var(--space-sm);
 }
 
-.selected-column-row {
+.selected-field-row {
     gap: var(--space-sm);
     padding: var(--space-xs) var(--space-sm);
     border: 1px solid var(--color-border-subtle, var(--color-bg-3));
@@ -28,22 +28,22 @@
     background: var(--color-bg-1, var(--color-bg-0));
 }
 
-.selected-column-main {
+.selected-field-main {
     min-width: 0;
     gap: var(--space-xs);
     flex: 1;
 }
 
-.selected-column-drag-handle {
+.selected-field-drag-handle {
     cursor: grab;
     color: var(--color-secondary-text);
 }
 
-.selected-column-drag-handle:active {
+.selected-field-drag-handle:active {
     cursor: grabbing;
 }
 
-.selected-column-label {
+.selected-field-label {
     min-width: 0;
     flex: 1;
     font-size: var(--font-size-sm);
@@ -52,7 +52,7 @@
     white-space: nowrap;
 }
 
-.selected-column-actions {
+.selected-field-actions {
     flex-shrink: 0;
     gap: var(--space-xxs);
 }
@@ -68,38 +68,3 @@
 .cdk-drag-placeholder {
     opacity: 0.35;
 }
-
-.highlight-color-list {
-    gap: var(--space-sm);
-}
-
-.highlight-color-label {
-    display: flex;
-    flex-direction: column;
-    min-width: 0;
-    font-size: var(--font-size-sm);
-}
-
-.highlight-color-measure {
-    color: var(--color-secondary-text);
-    font-size: var(--font-size-xs);
-}
-
-.highlight-color-picker {
-    margin-left: auto;
-    display: block;
-    width: calc(var(--space-md) * 2.4);
-    height: calc(var(--space-md) * 2.4);
-    padding: 0;
-    border: 1px solid var(--color-tab-border);
-    border-radius: var(--button-border-radius);
-    cursor: pointer;
-    appearance: none;
-    -webkit-appearance: none;
-    background-clip: padding-box;
-}
-
-.highlight-color-picker:disabled {
-    cursor: default;
-    opacity: 0.45;
-}
diff --git a/ui/src/app/chart-shared/components/chart-config/field-order-config/field-order-config.component.ts b/ui/src/app/chart-shared/components/chart-config/field-order-config/field-order-config.component.ts
new file mode 100644
index 0000000000..b9ce496a2c
--- /dev/null
+++ b/ui/src/app/chart-shared/components/chart-config/field-order-config/field-order-config.component.ts
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { DataExplorerField } from '@streampipes/platform-services';
+import {
+    CdkDrag,
+    CdkDragDrop,
+    CdkDragHandle,
+    CdkDropList,
+    moveItemInArray,
+} from '@angular/cdk/drag-drop';
+import {
+    LayoutAlignDirective,
+    LayoutDirective,
+} from '@ngbracket/ngx-layout/flex';
+import { MatIcon } from '@angular/material/icon';
+import { MatIconButton } from '@angular/material/button';
+import { SpAlertBannerComponent } from '@streampipes/shared-ui';
+import { TranslatePipe } from '@ngx-translate/core';
+import { SplitSectionComponent } from '@streampipes/shared-ui';
+
+@Component({
+    selector: 'sp-field-order-config',
+    templateUrl: './field-order-config.component.html',
+    styleUrls: ['./field-order-config.component.scss'],
+    imports: [
+        SplitSectionComponent,
+        CdkDropList,
+        CdkDrag,
+        CdkDragHandle,
+        LayoutDirective,
+        LayoutAlignDirective,
+        MatIcon,
+        MatIconButton,
+        SpAlertBannerComponent,
+        TranslatePipe,
+    ],
+})
+export class FieldOrderConfigComponent {
+    @Input() selectedFields: DataExplorerField[] = [];
+    @Input() sectionTitle = 'Field Order';
+    @Input() emptyMessage = 'No fields selected.';
+
+    @Output() selectedFieldsChange = new EventEmitter<DataExplorerField[]>();
+
+    dropSelectedField(event: CdkDragDrop<DataExplorerField[]>): void {
+        if (event.previousIndex === event.currentIndex) {
+            return;
+        }
+
+        const fields = [...(this.selectedFields ?? [])];
+        moveItemInArray(fields, event.previousIndex, event.currentIndex);
+        this.selectedFieldsChange.emit(fields);
+    }
+
+    moveSelectedField(fromIndex: number, offset: number): void {
+        const fields = [...(this.selectedFields ?? [])];
+        const targetIndex = fromIndex + offset;
+
+        if (
+            fromIndex < 0 ||
+            targetIndex < 0 ||
+            fromIndex >= fields.length ||
+            targetIndex >= fields.length
+        ) {
+            return;
+        }
+
+        const [movedField] = fields.splice(fromIndex, 1);
+        fields.splice(targetIndex, 0, movedField);
+        this.selectedFieldsChange.emit(fields);
+    }
+
+    canMoveSelectedFieldUp(index: number): boolean {
+        return index > 0;
+    }
+
+    canMoveSelectedFieldDown(index: number): boolean {
+        return index < (this.selectedFields?.length ?? 0) - 1;
+    }
+
+    selectedFieldLabel(field: DataExplorerField): string {
+        return `${field.runtimeName} (${field.measure})`;
+    }
+
+    fieldKey(field: DataExplorerField): string {
+        return `${field.fullDbName}:${field.sourceIndex}`;
+    }
+}
diff --git a/ui/src/app/chart-shared/components/chart-config/select-color-properties-config/time-series-item-config/time-series-item-config.component.html b/ui/src/app/chart-shared/components/chart-config/select-color-properties-config/time-series-item-config/time-series-item-config.component.html
index 1a64d5092d..ba578876af 100644
--- a/ui/src/app/chart-shared/components/chart-config/select-color-properties-config/time-series-item-config/time-series-item-config.component.html
+++ b/ui/src/app/chart-shared/components/chart-config/select-color-properties-config/time-series-item-config/time-series-item-config.component.html
@@ -91,10 +91,7 @@
                         class="w-100"
                     >
                         <input
-                            [(ngModel)]="
-                                currentlyConfiguredWidget.visualizationConfig
-                                    .displayName[getFieldKey(field)]
-                            "
+                            [ngModel]="getDisplayName(field)"
                             matInput
                             (input)="
                                 onDisplayNameChange($event.target.value, field)
diff --git a/ui/src/app/chart-shared/components/chart-config/select-color-properties-config/time-series-item-config/time-series-item-config.component.ts b/ui/src/app/chart-shared/components/chart-config/select-color-properties-config/time-series-item-config/time-series-item-config.component.ts
index fc23ef2c40..30ea20f873 100644
--- a/ui/src/app/chart-shared/components/chart-config/select-color-properties-config/time-series-item-config/time-series-item-config.component.ts
+++ b/ui/src/app/chart-shared/components/chart-config/select-color-properties-config/time-series-item-config/time-series-item-config.component.ts
@@ -41,6 +41,7 @@ import { MatOption, MatSelect } from '@angular/material/select';
 import { TranslatePipe } from '@ngx-translate/core';
 import { ColorMappingOptionsConfigComponent } from '../../color-mapping-options-config/color-mapping-options-config.component';
 import { LayoutDirective } from '@ngbracket/ngx-layout';
+import { ResultLabelService } from '../../../../services/result-label.service';
 
 @Component({
     selector: 'sp-time-series-item-config',
@@ -68,6 +69,8 @@ import { LayoutDirective } from '@ngbracket/ngx-layout';
     ],
 })
 export class SpTimeseriesItemConfigComponent {
+    constructor(private resultLabelService: ResultLabelService) {}
+
     @Input()
     field: DataExplorerField;
 
@@ -124,9 +127,14 @@ export class SpTimeseriesItemConfigComponent {
     }
 
     onDisplayNameChange(searchValue: string, field: DataExplorerField): void {
-        this.currentlyConfiguredWidget.visualizationConfig.displayName[
-            field.fullDbName + field.sourceIndex.toString()
-        ] = searchValue;
+        this.resultLabelService.setOverride(
+            this.currentlyConfiguredWidget.dataConfig.sourceConfigs[
+                field.sourceIndex
+            ].queryConfig,
+            field,
+            searchValue,
+            field.fullDbName,
+        );
         this.viewRefreshEmitter.emit();
     }
 
@@ -134,6 +142,18 @@ export class SpTimeseriesItemConfigComponent {
         return field.fullDbName + field.sourceIndex.toString();
     }
 
+    getDisplayName(field: DataExplorerField): string {
+        return this.resultLabelService.resolveLabel(
+            this.currentlyConfiguredWidget.dataConfig.sourceConfigs[
+                field.sourceIndex
+            ].queryConfig,
+            field,
+            this.currentlyConfiguredWidget.visualizationConfig.displayName[
+                this.getFieldKey(field)
+            ],
+        );
+    }
+
     hasGrouping(field: DataExplorerField): boolean {
         return (
             this.currentlyConfiguredWidget.dataConfig.sourceConfigs[
diff --git a/ui/src/app/chart-shared/components/charts/gauge/config/gauge-widget-config.component.html b/ui/src/app/chart-shared/components/charts/gauge/config/gauge-widget-config.component.html
index 389465b05c..70b38520ec 100644
--- a/ui/src/app/chart-shared/components/charts/gauge/config/gauge-widget-config.component.html
+++ b/ui/src/app/chart-shared/components/charts/gauge/config/gauge-widget-config.component.html
@@ -83,11 +83,8 @@
                     <input
                         matInput
                         type="text"
-                        [(ngModel)]="
-                            currentlyConfiguredWidget.visualizationConfig
-                                .displayName
-                        "
-                        (ngModelChange)="triggerViewRefresh()"
+                        [ngModel]="getDisplayName()"
+                        (ngModelChange)="onDisplayNameChange($event)"
                     />
                 </mat-form-field>
             </sp-form-field>
diff --git a/ui/src/app/chart-shared/components/charts/gauge/config/gauge-widget-config.component.ts b/ui/src/app/chart-shared/components/charts/gauge/config/gauge-widget-config.component.ts
index 5fc0916abc..db4057351a 100644
--- a/ui/src/app/chart-shared/components/charts/gauge/config/gauge-widget-config.component.ts
+++ b/ui/src/app/chart-shared/components/charts/gauge/config/gauge-widget-config.component.ts
@@ -32,6 +32,7 @@ import { MatInput } from '@angular/material/input';
 import { FormsModule } from '@angular/forms';
 import { TranslatePipe } from '@ngx-translate/core';
 import { MatCheckbox } from '@angular/material/checkbox';
+import { ResultLabelService } from '../../../../services/result-label.service';
 
 @Component({
     selector: 'sp-data-explorer-gauge-widget-config',
@@ -53,9 +54,58 @@ export class GaugeWidgetConfigComponent extends BaseWidgetConfig<
     GaugeWidgetModel,
     GaugeVisConfig
 > {
+    constructor(private resultLabelService: ResultLabelService) {
+        super();
+    }
+
     setSelectedProperty(field: DataExplorerField) {
         this.currentlyConfiguredWidget.visualizationConfig.selectedProperty =
             field;
+        this.currentlyConfiguredWidget.visualizationConfig.displayName =
+            this.resultLabelService.getOverride(
+                this.currentlyConfiguredWidget.dataConfig.sourceConfigs[
+                    field.sourceIndex
+                ].queryConfig,
+                field,
+            ) ??
+            field.runtimeName ??
+            field.fullDbName;
+        this.triggerViewRefresh();
+    }
+
+    getDisplayName(): string {
+        const selectedProperty =
+            this.currentlyConfiguredWidget.visualizationConfig.selectedProperty;
+
+        if (!selectedProperty) {
+            return '';
+        }
+
+        return this.resultLabelService.resolveLabel(
+            this.currentlyConfiguredWidget.dataConfig.sourceConfigs[
+                selectedProperty.sourceIndex
+            ].queryConfig,
+            selectedProperty,
+            this.currentlyConfiguredWidget.visualizationConfig.displayName,
+        );
+    }
+
+    onDisplayNameChange(label: string): void {
+        const selectedProperty =
+            this.currentlyConfiguredWidget.visualizationConfig.selectedProperty;
+
+        if (!selectedProperty) {
+            return;
+        }
+
+        this.resultLabelService.setOverride(
+            this.currentlyConfiguredWidget.dataConfig.sourceConfigs[
+                selectedProperty.sourceIndex
+            ].queryConfig,
+            selectedProperty,
+            label,
+            selectedProperty.fullDbName,
+        );
         this.triggerViewRefresh();
     }
 
diff --git a/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts b/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts
index b833ceba59..619c9c3b0d 100644
--- a/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts
+++ b/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts
@@ -31,6 +31,7 @@ import {
 import { WidgetSize } from '../../../models/dataset.model';
 import { EchartsBasicOptionsGeneratorService } from '../../../echarts-renderer/echarts-basic-options-generator.service';
 import { SpFieldUpdateService } from '../../../services/field-update.service';
+import { ResultLabelService } from '../../../services/result-label.service';
 
 @Injectable({ providedIn: 'root' })
 export class SpGaugeRendererService implements SpEchartsRenderer<GaugeWidgetModel> {
@@ -38,10 +39,11 @@ export class SpGaugeRendererService implements SpEchartsRenderer<GaugeWidgetMode
     protected echartsBaseOptionsGenerator = inject(
         EchartsBasicOptionsGeneratorService,
     );
+    protected resultLabelService = inject(ResultLabelService);
 
     makeSeriesItem(
         seriesName: string,
-        fieldName: string,
+        selectedField: DataExplorerField,
         value: number,
         decimals: number | undefined,
         widgetConfig: GaugeWidgetModel,
@@ -51,9 +53,14 @@ export class SpGaugeRendererService implements SpEchartsRenderer<GaugeWidgetMode
         const visConfig = widgetConfig.visualizationConfig;
         const clamp = this.getSizeClamp(widgetSize);
         const useThresholdColors = !!visConfig.enableThresholdColors;
-        const displayName = this.makeDisplayName(
-            visConfig.displayName,
-            fieldName,
+        const displayName = this.resultLabelService.resolveLabel(
+            widgetConfig.dataConfig.sourceConfigs[selectedField.sourceIndex]
+                .queryConfig,
+            selectedField,
+            this.makeDisplayName(
+                visConfig.displayName,
+                selectedField.fullDbName,
+            ),
         );
 
         const series: GaugeSeriesOption = {
@@ -172,7 +179,7 @@ export class SpGaugeRendererService implements SpEchartsRenderer<GaugeWidgetMode
             },
             series: this.makeSeriesItem(
                 '',
-                selectedField.fullDbName,
+                selectedField,
                 data,
                 decimals,
                 widgetConfig,
diff --git a/ui/src/app/chart-shared/components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component.html b/ui/src/app/chart-shared/components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component.html
new file mode 100644
index 0000000000..1abed96706
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component.html
@@ -0,0 +1,46 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<sp-split-section [level]="3" [title]="'Bar settings' | translate">
+    <div fxLayout="column" class="progress-color-list">
+        <sp-form-field [level]="3" [label]="'Progress color' | translate">
+            <input
+                class="progress-color-picker"
+                [colorPicker]="config.progressColor"
+                [style.background]="config.progressColor"
+                [cpPosition]="'auto'"
+                [cpPresetColors]="presetColors"
+                (colorPickerChange)="setColor('progressColor', $event)"
+            />
+        </sp-form-field>
+
+        <sp-form-field [level]="3" [label]="'Track color' | translate">
+            <input
+                class="progress-color-picker"
+                [colorPicker]="config.trackColor"
+                [style.background]="config.trackColor"
+                [cpPosition]="'auto'"
+                [cpPresetColors]="presetColors"
+                (colorPickerChange)="setColor('trackColor', $event)"
+            />
+        </sp-form-field>
+    </div>
+
+    <sp-number-format-config [appearanceConfig]="appearanceConfig">
+    </sp-number-format-config>
+</sp-split-section>
diff --git a/ui/src/app/chart-shared/components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component.scss b/ui/src/app/chart-shared/components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component.scss
new file mode 100644
index 0000000000..ddc3d05f1e
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component.scss
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+.progress-color-list {
+    gap: var(--space-sm);
+}
+
+.progress-color-picker {
+    display: block;
+    width: calc(var(--space-md) * 2.4);
+    height: calc(var(--space-md) * 2.4);
+    padding: 0;
+    border: 1px solid var(--color-tab-border);
+    border-radius: var(--button-border-radius);
+    cursor: pointer;
+    appearance: none;
+    -webkit-appearance: none;
+    background-clip: padding-box;
+}
diff --git a/ui/src/app/chart-shared/components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component.ts b/ui/src/app/chart-shared/components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component.ts
new file mode 100644
index 0000000000..606fb8b18e
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component.ts
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, Input, OnInit, inject } from '@angular/core';
+import {
+    FormFieldComponent,
+    SplitSectionComponent,
+} from '@streampipes/shared-ui';
+import { TranslatePipe } from '@ngx-translate/core';
+import { SpNumberFormatConfigComponent } from '../../../chart-config/number-format-config/number-format-config.component';
+import { ProgressBarAppearanceConfig } from '../model/progress-bar-widget.model';
+import { ChartConfigurationService } from '../../../../services/chart-configuration.service';
+import { ColorPickerDirective } from 'ngx-color-picker';
+import { FlexDirective } from '@ngbracket/ngx-layout/flex';
+
+@Component({
+    selector: 'sp-progress-bar-widget-appearance-config',
+    templateUrl: './progress-bar-appearance-config.component.html',
+    styleUrls: ['./progress-bar-appearance-config.component.scss'],
+    imports: [
+        SplitSectionComponent,
+        FormFieldComponent,
+        TranslatePipe,
+        SpNumberFormatConfigComponent,
+        ColorPickerDirective,
+        FlexDirective,
+    ],
+})
+export class ProgressBarWidgetAppearanceConfigComponent implements OnInit {
+    private widgetConfigurationService = inject(ChartConfigurationService);
+
+    @Input()
+    appearanceConfig: ProgressBarAppearanceConfig;
+
+    readonly presetColors = [
+        '#2563EB',
+        '#16A34A',
+        '#F59E0B',
+        '#DC2626',
+        '#0F766E',
+        '#9333EA',
+    ];
+
+    ngOnInit(): void {
+        this.ensureAppearanceConfig();
+    }
+
+    get config(): ProgressBarAppearanceConfig {
+        return this.ensureAppearanceConfig();
+    }
+
+    setColor(key: 'progressColor' | 'trackColor', color: string): void {
+        this.config[key] = color;
+        this.widgetConfigurationService.notify({
+            refreshView: true,
+            refreshData: false,
+        });
+    }
+
+    private ensureAppearanceConfig(): ProgressBarAppearanceConfig {
+        this.appearanceConfig ??= {
+            backgroundColor: 'var(--color-bg-0)',
+            textColor: 'var(--color-default-text)',
+            widgetTitle: '',
+            numberFormat: {},
+        };
+
+        this.appearanceConfig.progressColor ??= '#2563EB';
+        this.appearanceConfig.trackColor ??= 'rgba(148, 163, 184, 0.24)';
+
+        return this.appearanceConfig;
+    }
+}
diff --git a/ui/src/app/chart-shared/components/charts/progress-bar/config/progress-bar-widget-config.component.html b/ui/src/app/chart-shared/components/charts/progress-bar/config/progress-bar-widget-config.component.html
new file mode 100644
index 0000000000..07eef853c4
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/progress-bar/config/progress-bar-widget-config.component.html
@@ -0,0 +1,159 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<sp-visualization-config-outer
+    [configurationValid]="
+        currentlyConfiguredWidget.visualizationConfig.configurationValid
+    "
+>
+    <sp-split-section [level]="3" [title]="'Values' | translate">
+        <sp-form-field [level]="3" [label]="'Current value field' | translate">
+            <sp-select-single-property-config
+                [availableProperties]="availableNumericFields"
+                [selectedProperty]="
+                    currentlyConfiguredWidget.visualizationConfig
+                        .currentValueField
+                "
+                (changeSelectedProperty)="setCurrentValueField($event)"
+            >
+            </sp-select-single-property-config>
+        </sp-form-field>
+
+        <sp-form-field [level]="3" [label]="'Target source' | translate">
+            <mat-radio-group
+                [ngModel]="
+                    currentlyConfiguredWidget.visualizationConfig.targetSource
+                "
+                (ngModelChange)="setTargetSource($event)"
+            >
+                <mat-radio-button [value]="'fixed'">
+                    {{ 'Fixed value' | translate }}
+                </mat-radio-button>
+                <mat-radio-button [value]="'field'">
+                    {{ 'Field' | translate }}
+                </mat-radio-button>
+            </mat-radio-group>
+        </sp-form-field>
+
+        @if (
+            currentlyConfiguredWidget.visualizationConfig.targetSource ===
+            'field'
+        ) {
+            <sp-form-field [level]="3" [label]="'Target field' | translate">
+                <sp-select-single-property-config
+                    [availableProperties]="availableNumericFields"
+                    [selectedProperty]="
+                        currentlyConfiguredWidget.visualizationConfig
+                            .targetField
+                    "
+                    (changeSelectedProperty)="setTargetField($event)"
+                >
+                </sp-select-single-property-config>
+            </sp-form-field>
+        } @else {
+            <sp-form-field [level]="3" [label]="'Target value' | translate">
+                <mat-form-field fxFlex="100">
+                    <input
+                        matInput
+                        type="number"
+                        [ngModel]="
+                            currentlyConfiguredWidget.visualizationConfig
+                                .targetValue
+                        "
+                        (ngModelChange)="setTargetValue($event)"
+                    />
+                </mat-form-field>
+            </sp-form-field>
+        }
+    </sp-split-section>
+
+    <sp-split-section [level]="3" [title]="'Behavior' | translate">
+        <sp-form-field [level]="3" [label]="'Display mode' | translate">
+            <mat-radio-group
+                [ngModel]="
+                    currentlyConfiguredWidget.visualizationConfig.displayMode
+                "
+                (ngModelChange)="setDisplayMode($event)"
+            >
+                <mat-radio-button [value]="'percent'">
+                    {{ 'Percent only' | translate }}
+                </mat-radio-button>
+                <mat-radio-button [value]="'value'">
+                    {{ 'Current / Target' | translate }}
+                </mat-radio-button>
+                <mat-radio-button [value]="'percent-and-value'">
+                    {{ 'Percent + Current / Target' | translate }}
+                </mat-radio-button>
+            </mat-radio-group>
+        </sp-form-field>
+
+        <mat-checkbox
+            [checked]="currentlyConfiguredWidget.visualizationConfig.showLabel"
+            (change)="setShowLabel($event.checked)"
+        >
+            {{ 'Show progress label' | translate }}
+        </mat-checkbox>
+
+        <mat-checkbox
+            [checked]="
+                currentlyConfiguredWidget.visualizationConfig.invertProgress
+            "
+            (change)="setInvertProgress($event.checked)"
+        >
+            {{ 'Invert progress semantics' | translate }}
+        </mat-checkbox>
+
+        <mat-checkbox
+            [checked]="
+                currentlyConfiguredWidget.visualizationConfig.clampProgress
+            "
+            (change)="setClampProgress($event.checked)"
+        >
+            {{ 'Clamp to 0-100%' | translate }}
+        </mat-checkbox>
+    </sp-split-section>
+
+    <sp-split-section [level]="3" [title]="'Content' | translate">
+        <sp-form-field [level]="3" [label]="'Title' | translate">
+            <mat-form-field fxFlex="100">
+                <input
+                    matInput
+                    type="text"
+                    [(ngModel)]="
+                        currentlyConfiguredWidget.visualizationConfig.title
+                    "
+                    (ngModelChange)="triggerViewRefresh()"
+                />
+            </mat-form-field>
+        </sp-form-field>
+
+        <sp-form-field [level]="3" [label]="'Description' | translate">
+            <mat-form-field fxFlex="100">
+                <textarea
+                    matInput
+                    rows="3"
+                    [(ngModel)]="
+                        currentlyConfiguredWidget.visualizationConfig
+                            .description
+                    "
+                    (ngModelChange)="triggerViewRefresh()"
+                ></textarea>
+            </mat-form-field>
+        </sp-form-field>
+    </sp-split-section>
+</sp-visualization-config-outer>
diff --git a/ui/src/app/chart-shared/components/charts/progress-bar/config/progress-bar-widget-config.component.ts b/ui/src/app/chart-shared/components/charts/progress-bar/config/progress-bar-widget-config.component.ts
new file mode 100644
index 0000000000..bea77b375f
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/progress-bar/config/progress-bar-widget-config.component.ts
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component } from '@angular/core';
+import { BaseWidgetConfig } from '../../base/base-widget-config';
+import {
+    ProgressBarDisplayMode,
+    ProgressBarTargetSource,
+    ProgressBarVisConfig,
+    ProgressBarWidgetModel,
+} from '../model/progress-bar-widget.model';
+import { DataExplorerField } from '@streampipes/platform-services';
+import { SpVisualizationConfigOuterComponent } from '../../../chart-config/visualization-config-outer/visualization-config-outer.component';
+import {
+    FormFieldComponent,
+    SplitSectionComponent,
+} from '@streampipes/shared-ui';
+import { SelectSinglePropertyConfigComponent } from '../../../chart-config/select-single-property-config/select-single-property-config.component';
+import { MatFormField } from '@angular/material/form-field';
+import { MatInput } from '@angular/material/input';
+import { FormsModule } from '@angular/forms';
+import { MatCheckbox } from '@angular/material/checkbox';
+import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
+import { FlexDirective } from '@ngbracket/ngx-layout/flex';
+import { TranslatePipe } from '@ngx-translate/core';
+
+@Component({
+    selector: 'sp-data-explorer-progress-bar-widget-config',
+    templateUrl: './progress-bar-widget-config.component.html',
+    imports: [
+        SpVisualizationConfigOuterComponent,
+        SplitSectionComponent,
+        FormFieldComponent,
+        SelectSinglePropertyConfigComponent,
+        MatFormField,
+        MatInput,
+        MatCheckbox,
+        MatRadioGroup,
+        MatRadioButton,
+        FlexDirective,
+        FormsModule,
+        TranslatePipe,
+    ],
+})
+export class ProgressBarWidgetConfigComponent extends BaseWidgetConfig<
+    ProgressBarWidgetModel,
+    ProgressBarVisConfig
+> {
+    get availableNumericFields(): DataExplorerField[] {
+        const primarySourceFields = this.fieldProvider.numericFields.filter(
+            field => field.sourceIndex === 0,
+        );
+        return primarySourceFields.length > 0
+            ? primarySourceFields
+            : this.fieldProvider.numericFields;
+    }
+
+    setCurrentValueField(field: DataExplorerField): void {
+        this.currentlyConfiguredWidget.visualizationConfig.currentValueField =
+            field;
+        this.triggerViewRefresh();
+    }
+
+    setTargetSource(targetSource: ProgressBarTargetSource): void {
+        this.currentlyConfiguredWidget.visualizationConfig.targetSource =
+            targetSource;
+
+        if (
+            targetSource === 'field' &&
+            !this.currentlyConfiguredWidget.visualizationConfig.targetField
+        ) {
+            this.currentlyConfiguredWidget.visualizationConfig.targetField =
+                this.availableNumericFields[0];
+        }
+
+        this.triggerViewRefresh();
+    }
+
+    setTargetField(field: DataExplorerField): void {
+        this.currentlyConfiguredWidget.visualizationConfig.targetField = field;
+        this.triggerViewRefresh();
+    }
+
+    setTargetValue(targetValue: string | number): void {
+        const numericValue = Number(String(targetValue).replace(',', '.'));
+        this.currentlyConfiguredWidget.visualizationConfig.targetValue =
+            Number.isFinite(numericValue) ? numericValue : undefined;
+        this.triggerViewRefresh();
+    }
+
+    setInvertProgress(invertProgress: boolean): void {
+        this.currentlyConfiguredWidget.visualizationConfig.invertProgress =
+            invertProgress;
+        this.triggerViewRefresh();
+    }
+
+    setClampProgress(clampProgress: boolean): void {
+        this.currentlyConfiguredWidget.visualizationConfig.clampProgress =
+            clampProgress;
+        this.triggerViewRefresh();
+    }
+
+    setDisplayMode(displayMode: ProgressBarDisplayMode): void {
+        this.currentlyConfiguredWidget.visualizationConfig.displayMode =
+            displayMode;
+        this.triggerViewRefresh();
+    }
+
+    setShowLabel(showLabel: boolean): void {
+        this.currentlyConfiguredWidget.visualizationConfig.showLabel =
+            showLabel;
+        this.triggerViewRefresh();
+    }
+
+    protected applyWidgetConfig(config: ProgressBarVisConfig): void {
+        config.currentValueField = this.fieldService.getSelectedField(
+            config.currentValueField,
+            this.availableNumericFields,
+            () => this.availableNumericFields[0],
+        );
+        config.targetSource ??= 'fixed';
+        config.targetField = this.fieldService.getSelectedField(
+            config.targetField,
+            this.availableNumericFields,
+            () => this.availableNumericFields[0],
+        );
+        config.targetValue ??= 100;
+        config.invertProgress ??= false;
+        config.clampProgress ??= true;
+        config.displayMode ??= 'percent-and-value';
+        config.showLabel ??= true;
+        config.title ??= '';
+        config.description ??= '';
+    }
+
+    protected requiredFieldsForChartPresent(): boolean {
+        return this.availableNumericFields.length > 0;
+    }
+}
diff --git a/ui/src/app/chart-shared/components/charts/progress-bar/model/progress-bar-widget.model.ts b/ui/src/app/chart-shared/components/charts/progress-bar/model/progress-bar-widget.model.ts
new file mode 100644
index 0000000000..678980565e
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/progress-bar/model/progress-bar-widget.model.ts
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import {
+    DataExplorerDataConfig,
+    DataExplorerField,
+    DataExplorerWidgetModel,
+} from '@streampipes/platform-services';
+import {
+    DataExplorerVisConfig,
+    WidgetNumberAppearanceConfig,
+} from '../../../../models/dataview-dashboard.model';
+
+export type ProgressBarTargetSource = 'fixed' | 'field';
+export type ProgressBarDisplayMode = 'percent' | 'value' | 'percent-and-value';
+
+export interface ProgressBarVisConfig extends DataExplorerVisConfig {
+    currentValueField?: DataExplorerField;
+    targetSource?: ProgressBarTargetSource;
+    targetField?: DataExplorerField;
+    targetValue?: number;
+    invertProgress?: boolean;
+    clampProgress?: boolean;
+    displayMode?: ProgressBarDisplayMode;
+    showLabel?: boolean;
+    title?: string;
+    description?: string;
+}
+
+export interface ProgressBarAppearanceConfig extends WidgetNumberAppearanceConfig {
+    progressColor?: string;
+    trackColor?: string;
+}
+
+export interface ProgressBarWidgetModel extends DataExplorerWidgetModel {
+    dataConfig: DataExplorerDataConfig;
+    visualizationConfig: ProgressBarVisConfig;
+    baseAppearanceConfig: ProgressBarAppearanceConfig;
+}
diff --git a/ui/src/app/chart-shared/components/charts/progress-bar/progress-bar-widget.component.html b/ui/src/app/chart-shared/components/charts/progress-bar/progress-bar-widget.component.html
new file mode 100644
index 0000000000..e5dc11771b
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/progress-bar/progress-bar-widget.component.html
@@ -0,0 +1,112 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<div
+    class="progress-widget"
+    fxLayout="column"
+    fxFlex="100"
+    [ngStyle]="widgetStyles"
+    data-cy="progress-bar-widget"
+>
+    @if (showNoDataInDateRange) {
+        <sp-no-data-in-date-range [viewDateRange]="timeSettings" class="h-100">
+        </sp-no-data-in-date-range>
+    }
+
+    @if (showTooMuchData) {
+        <sp-too-much-data
+            [amountOfEvents]="amountOfTooMuchEvents"
+            (loadDataWithTooManyEventsEmitter)="loadDataWithTooManyEvents()"
+            class="h-100"
+        >
+        </sp-too-much-data>
+    }
+
+    @if (showInvalidConfiguration) {
+        <sp-invalid-configuration
+            [widgetTypeLabel]="widgetTypeLabel"
+            class="h-100"
+        >
+        </sp-invalid-configuration>
+    }
+
+    @if (showData && progressView) {
+        <div class="progress-shell" fxLayout="column" fxFlex="100">
+            @if (titleText || descriptionText) {
+                <div class="progress-copy" fxLayout="column">
+                    @if (titleText) {
+                        <div class="progress-title">
+                            {{ titleText }}
+                        </div>
+                    }
+
+                    @if (descriptionText) {
+                        <div class="progress-description">
+                            {{ descriptionText }}
+                        </div>
+                    }
+                </div>
+            }
+
+            <div
+                class="progress-meta"
+                fxLayout="row"
+                fxLayoutAlign="space-between center"
+            >
+                <span class="progress-status">{{
+                    progressView.statusLabel
+                }}</span>
+                @if (showLabel) {
+                    <span class="progress-percent">{{
+                        progressView.percentLabel
+                    }}</span>
+                }
+            </div>
+
+            <div class="progress-track">
+                <div class="progress-fill" [style.width]="progressWidth"></div>
+            </div>
+
+            @if (showLabel) {
+                <div class="progress-labels" fxLayout="column">
+                    @if (
+                        dataExplorerWidget.visualizationConfig.displayMode ===
+                            'percent' ||
+                        dataExplorerWidget.visualizationConfig.displayMode ===
+                            'percent-and-value'
+                    ) {
+                        <div class="progress-primary-label">
+                            {{ progressView.percentLabel }}
+                        </div>
+                    }
+
+                    @if (
+                        dataExplorerWidget.visualizationConfig.displayMode ===
+                            'value' ||
+                        dataExplorerWidget.visualizationConfig.displayMode ===
+                            'percent-and-value'
+                    ) {
+                        <div class="progress-secondary-label">
+                            {{ progressView.valueLabel }}
+                        </div>
+                    }
+                </div>
+            }
+        </div>
+    }
+</div>
diff --git a/ui/src/app/chart-shared/components/charts/progress-bar/progress-bar-widget.component.scss b/ui/src/app/chart-shared/components/charts/progress-bar/progress-bar-widget.component.scss
new file mode 100644
index 0000000000..b25c8eee61
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/progress-bar/progress-bar-widget.component.scss
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+:host {
+    display: block;
+    height: 100%;
+}
+
+.h-100 {
+    height: 100%;
+}
+
+.progress-widget {
+    box-sizing: border-box;
+    height: 100%;
+    min-height: 0;
+    overflow: hidden;
+    padding: var(--progress-widget-padding);
+}
+
+.progress-shell {
+    gap: var(--progress-shell-gap);
+    min-height: 0;
+}
+
+.progress-copy {
+    gap: 0.35rem;
+}
+
+.progress-title {
+    font-size: var(--progress-title-size);
+    font-weight: 700;
+    letter-spacing: -0.03em;
+    line-height: 1.08;
+}
+
+.progress-description {
+    color: color-mix(in srgb, currentColor 70%, transparent);
+    font-size: var(--progress-description-size);
+    line-height: 1.35;
+    max-width: 48ch;
+}
+
+.progress-meta {
+    column-gap: 1rem;
+}
+
+.progress-status {
+    color: color-mix(in srgb, currentColor 68%, transparent);
+    font-size: var(--progress-percent-size);
+    font-weight: 700;
+}
+
+.progress-percent {
+    font-size: var(--progress-percent-size);
+    font-weight: 700;
+}
+
+.progress-track {
+    background: var(--progress-bar-track-color);
+    border-radius: 999px;
+    flex-shrink: 0;
+    height: var(--progress-track-height);
+    overflow: hidden;
+    position: relative;
+}
+
+.progress-fill {
+    background: var(--progress-bar-fill-color);
+    border-radius: inherit;
+    height: 100%;
+    min-width: 0;
+    transition: width 220ms ease-out;
+}
+
+.progress-labels {
+    gap: 0.2rem;
+}
+
+.progress-primary-label {
+    font-size: var(--progress-primary-label-size);
+    font-weight: 700;
+    letter-spacing: -0.04em;
+    line-height: 1;
+}
+
+.progress-secondary-label {
+    color: color-mix(in srgb, currentColor 70%, transparent);
+    font-size: var(--progress-secondary-label-size);
+    font-weight: 600;
+    line-height: 1.3;
+}
diff --git a/ui/src/app/chart-shared/components/charts/progress-bar/progress-bar-widget.component.ts b/ui/src/app/chart-shared/components/charts/progress-bar/progress-bar-widget.component.ts
new file mode 100644
index 0000000000..4d42c12d6a
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/progress-bar/progress-bar-widget.component.ts
@@ -0,0 +1,376 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { NgStyle } from '@angular/common';
+import { Component, LOCALE_ID, OnInit, inject } from '@angular/core';
+import {
+    DataExplorerField,
+    SpQueryResult,
+} from '@streampipes/platform-services';
+import { FlexDirective, LayoutDirective } from '@ngbracket/ngx-layout/flex';
+import { BaseDataExplorerWidgetDirective } from '../base/base-data-explorer-widget.directive';
+import { NoDataInDateRangeComponent } from '../base/no-data/no-data-in-date-range.component';
+import { TooMuchDataComponent } from '../base/too-much-data/too-much-data.component';
+import { SpInvalidConfigurationComponent } from '../base/invalid-configuration/invalid-configuration.component';
+import { ChartRegistry } from '../../../registry/chart-registry.service';
+import {
+    ProgressBarAppearanceConfig,
+    ProgressBarTargetSource,
+    ProgressBarWidgetModel,
+} from './model/progress-bar-widget.model';
+import {
+    clampValue,
+    formatWidgetNumber,
+    scaleResponsiveValue,
+} from '../../../services/widget-render.utils';
+
+interface ProgressBarViewModel {
+    ratio: number;
+    percentLabel: string;
+    valueLabel: string;
+    statusLabel: string;
+}
+
+@Component({
+    selector: 'sp-data-explorer-progress-bar-widget',
+    templateUrl: './progress-bar-widget.component.html',
+    styleUrls: ['./progress-bar-widget.component.scss'],
+    imports: [
+        LayoutDirective,
+        FlexDirective,
+        NgStyle,
+        NoDataInDateRangeComponent,
+        TooMuchDataComponent,
+        SpInvalidConfigurationComponent,
+    ],
+})
+export class ProgressBarWidgetComponent
+    extends BaseDataExplorerWidgetDirective<ProgressBarWidgetModel>
+    implements OnInit
+{
+    widgetTypeLabel: string;
+    progressView?: ProgressBarViewModel;
+
+    private hasReceivedData = false;
+    private latestData: SpQueryResult[] = [];
+    private readonly locale = inject(LOCALE_ID);
+    private readonly chartRegistry = inject(ChartRegistry);
+
+    ngOnInit(): void {
+        super.ngOnInit();
+        this.widgetTypeLabel = this.chartRegistry.getChartTemplate(
+            this.dataExplorerWidget.widgetType,
+        ).label;
+    }
+
+    get appearanceConfig(): ProgressBarAppearanceConfig {
+        this.dataExplorerWidget.baseAppearanceConfig ??= {
+            backgroundColor: 'var(--color-bg-0)',
+            textColor: 'var(--color-default-text)',
+            widgetTitle: '',
+            numberFormat: {},
+            progressColor: '#2563EB',
+            trackColor: 'rgba(148, 163, 184, 0.24)',
+        };
+
+        return this.dataExplorerWidget
+            .baseAppearanceConfig as ProgressBarAppearanceConfig;
+    }
+
+    get widgetStyles(): Record<string, string> {
+        const compactMode =
+            (this.currentWidth ?? 0) < 520 || (this.currentHeight ?? 0) < 220;
+        const fontScale = this.getFontScale();
+
+        return {
+            'background': this.appearanceConfig.backgroundColor,
+            'color': this.appearanceConfig.textColor,
+            '--progress-widget-padding': compactMode
+                ? '0.75rem 0.85rem'
+                : '1rem 1.1rem',
+            '--progress-shell-gap': `${scaleResponsiveValue(
+                8,
+                16,
+                fontScale,
+                0.55,
+                1.35,
+            )}px`,
+            '--progress-title-size': `${scaleResponsiveValue(
+                16,
+                20,
+                fontScale,
+                0.55,
+                1.35,
+            )}px`,
+            '--progress-description-size': `${scaleResponsiveValue(
+                12,
+                14,
+                fontScale,
+                0.55,
+                1.35,
+            )}px`,
+            '--progress-status-size': `${scaleResponsiveValue(
+                10,
+                13,
+                fontScale,
+                0.55,
+                1.35,
+            )}px`,
+            '--progress-percent-size': `${scaleResponsiveValue(
+                12,
+                15,
+                fontScale,
+                0.55,
+                1.35,
+            )}px`,
+            '--progress-track-height': `${scaleResponsiveValue(
+                10,
+                16,
+                fontScale,
+                0.55,
+                1.35,
+            )}px`,
+            '--progress-primary-label-size': `${scaleResponsiveValue(
+                18,
+                38,
+                fontScale,
+                0.55,
+                1.35,
+            )}px`,
+            '--progress-secondary-label-size': `${scaleResponsiveValue(
+                12,
+                15,
+                fontScale,
+                0.55,
+                1.35,
+            )}px`,
+            '--progress-bar-fill-color':
+                this.appearanceConfig.progressColor ?? '#2563EB',
+            '--progress-bar-track-color':
+                this.appearanceConfig.trackColor ?? 'rgba(148, 163, 184, 0.24)',
+        };
+    }
+
+    get titleText(): string {
+        return this.dataExplorerWidget.visualizationConfig.title?.trim() ?? '';
+    }
+
+    get descriptionText(): string {
+        return (
+            this.dataExplorerWidget.visualizationConfig.description?.trim() ??
+            ''
+        );
+    }
+
+    get showLabel(): boolean {
+        return !!this.dataExplorerWidget.visualizationConfig.showLabel;
+    }
+
+    get progressWidth(): string {
+        return `${clampValue((this.progressView?.ratio ?? 0) * 100, 0, 100)}%`;
+    }
+
+    beforeDataFetched(): void {
+        if (this.progressView) {
+            this.setShownComponents(false, true, false, false);
+            return;
+        }
+
+        this.setShownComponents(false, false, true, false);
+    }
+
+    onDataReceived(spQueryResults: SpQueryResult[]): void {
+        this.hasReceivedData = true;
+        this.latestData = spQueryResults;
+        this.refreshView();
+    }
+
+    onResize(_width: number, _height: number): void {
+        this.refreshView();
+    }
+
+    refreshView(): void {
+        this.updateProgressView();
+    }
+
+    handleUpdatedFields(
+        addedFields: DataExplorerField[],
+        removedFields: DataExplorerField[],
+    ): void {
+        const fieldUpdateInfo = {
+            addedFields,
+            removedFields,
+            fieldProvider: this.fieldProvider,
+        };
+
+        this.dataExplorerWidget.visualizationConfig.currentValueField =
+            this.fieldUpdateService.updateNumericField(
+                this.dataExplorerWidget.visualizationConfig.currentValueField,
+                fieldUpdateInfo,
+            );
+        this.dataExplorerWidget.visualizationConfig.targetField =
+            this.fieldUpdateService.updateNumericField(
+                this.dataExplorerWidget.visualizationConfig.targetField,
+                fieldUpdateInfo,
+            );
+        this.refreshView();
+    }
+
+    private updateProgressView(): void {
+        if (!this.isConfigurationValid()) {
+            this.showInvalidConfiguration = true;
+            this.progressView = undefined;
+            this.setShownComponents(false, false, false, false);
+            return;
+        }
+
+        this.showInvalidConfiguration = false;
+        if (!this.hasReceivedData && this.latestData.length === 0) {
+            return;
+        }
+
+        const firstQueryResult = this.latestData[0];
+        const firstRow = this.getFirstRow(firstQueryResult);
+
+        if (!firstQueryResult?.headers?.length || !firstRow) {
+            this.progressView = undefined;
+            this.setShownComponents(true, false, false, false);
+            return;
+        }
+
+        const currentValue = this.getFieldValue(
+            firstQueryResult,
+            firstRow,
+            this.dataExplorerWidget.visualizationConfig.currentValueField,
+        );
+        const targetValue = this.getTargetValue(firstQueryResult, firstRow);
+
+        if (
+            currentValue === undefined ||
+            targetValue === undefined ||
+            !Number.isFinite(currentValue) ||
+            !Number.isFinite(targetValue) ||
+            targetValue <= 0
+        ) {
+            this.showInvalidConfiguration = true;
+            this.progressView = undefined;
+            this.setShownComponents(false, false, false, false);
+            return;
+        }
+
+        const rawRatio = currentValue / targetValue;
+        const inverted =
+            this.dataExplorerWidget.visualizationConfig.invertProgress;
+        const unclampedRatio = inverted ? 1 - rawRatio : rawRatio;
+        const ratio = this.dataExplorerWidget.visualizationConfig.clampProgress
+            ? clampValue(unclampedRatio, 0, 1)
+            : unclampedRatio;
+
+        this.progressView = {
+            ratio,
+            percentLabel: `${formatWidgetNumber(
+                ratio * 100,
+                this.locale,
+                this.appearanceConfig.numberFormat?.decimals,
+            )}%`,
+            valueLabel: `${formatWidgetNumber(
+                currentValue,
+                this.locale,
+                this.appearanceConfig.numberFormat?.decimals,
+            )} / ${formatWidgetNumber(
+                targetValue,
+                this.locale,
+                this.appearanceConfig.numberFormat?.decimals,
+            )}`,
+            statusLabel: inverted ? 'Remaining' : 'Completed',
+        };
+
+        this.setShownComponents(false, true, false, false);
+    }
+
+    private isConfigurationValid(): boolean {
+        const config = this.dataExplorerWidget.visualizationConfig;
+
+        if (!config.currentValueField) {
+            return false;
+        }
+
+        if (config.targetSource === 'field') {
+            return !!config.targetField;
+        }
+
+        return config.targetValue !== undefined && config.targetValue !== null;
+    }
+
+    private getTargetValue(
+        queryResult: SpQueryResult,
+        row: unknown[],
+    ): number | undefined {
+        const targetSource = this.dataExplorerWidget.visualizationConfig
+            .targetSource as ProgressBarTargetSource;
+
+        if (targetSource === 'field') {
+            return this.getFieldValue(
+                queryResult,
+                row,
+                this.dataExplorerWidget.visualizationConfig.targetField,
+            );
+        }
+
+        return this.dataExplorerWidget.visualizationConfig.targetValue;
+    }
+
+    private getFieldValue(
+        queryResult: SpQueryResult,
+        row: unknown[],
+        field?: DataExplorerField,
+    ): number | undefined {
+        if (!field) {
+            return undefined;
+        }
+
+        const fieldIndex = queryResult.headers.findIndex(
+            header => header === field.fullDbName,
+        );
+        if (fieldIndex < 0) {
+            return undefined;
+        }
+
+        const value = Number(row[fieldIndex]);
+        return Number.isFinite(value) ? value : undefined;
+    }
+
+    private getFirstRow(queryResult?: SpQueryResult): unknown[] | undefined {
+        return queryResult?.allDataSeries.find(series => series.rows.length > 0)
+            ?.rows[0];
+    }
+
+    private getFontScale(): number {
+        const width = this.currentWidth ?? 0;
+        const height = this.currentHeight ?? 0;
+
+        if (width <= 0 || height <= 0) {
+            return 0.85;
+        }
+
+        const widthScale = clampValue(width / 420, 0.6, 1.35);
+        const heightScale = clampValue(height / 260, 0.55, 1.35);
+
+        return clampValue(Math.min(widthScale, heightScale), 0.55, 1.35);
+    }
+}
diff --git a/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.html b/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.html
index fa8808c9e9..7b0a9e1772 100644
--- a/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.html
+++ b/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.html
@@ -31,86 +31,14 @@
         "
     >
     </sp-select-properties-config>
-    <sp-split-section [level]="3" [title]="'Column Order' | translate">
-        <div
-            fxLayout="column"
-            class="selected-column-list"
-            cdkDropList
-            [cdkDropListData]="
-                currentlyConfiguredWidget.visualizationConfig.selectedColumns
-            "
-            (cdkDropListDropped)="dropSelectedColumn($event)"
-        >
-            @for (
-                field of currentlyConfiguredWidget.visualizationConfig
-                    .selectedColumns;
-                track colorKey(field);
-                let i = $index
-            ) {
-                <div
-                    class="selected-column-row"
-                    fxLayout="row"
-                    fxLayoutAlign="space-between center"
-                    cdkDrag
-                >
-                    <div
-                        class="selected-column-main"
-                        fxLayout="row"
-                        fxLayoutAlign="start center"
-                    >
-                        <button
-                            mat-icon-button
-                            type="button"
-                            class="selected-column-drag-handle"
-                            cdkDragHandle
-                            [attr.aria-label]="
-                                'Reorder ' + field.runtimeName | translate
-                            "
-                        >
-                            <mat-icon>drag_indicator</mat-icon>
-                        </button>
-                        <div class="selected-column-label">
-                            {{ selectedColumnLabel(field) }}
-                        </div>
-                    </div>
-                    <div
-                        class="selected-column-actions"
-                        fxLayout="row"
-                        fxLayoutAlign="end center"
-                    >
-                        <button
-                            mat-icon-button
-                            type="button"
-                            [disabled]="!canMoveSelectedColumnUp(i)"
-                            [attr.aria-label]="
-                                'Move ' + field.runtimeName + ' up' | translate
-                            "
-                            (click)="moveSelectedColumn(i, -1)"
-                        >
-                            <mat-icon>arrow_upward</mat-icon>
-                        </button>
-                        <button
-                            mat-icon-button
-                            type="button"
-                            [disabled]="!canMoveSelectedColumnDown(i)"
-                            [attr.aria-label]="
-                                'Move ' + field.runtimeName + ' down'
-                                    | translate
-                            "
-                            (click)="moveSelectedColumn(i, 1)"
-                        >
-                            <mat-icon>arrow_downward</mat-icon>
-                        </button>
-                    </div>
-                </div>
-            } @empty {
-                <sp-alert-banner
-                    type="info"
-                    [title]="'No fields selected.' | translate"
-                ></sp-alert-banner>
-            }
-        </div>
-    </sp-split-section>
+    <sp-field-order-config
+        [sectionTitle]="'Column Order'"
+        [selectedFields]="
+            currentlyConfiguredWidget.visualizationConfig.selectedColumns
+        "
+        (selectedFieldsChange)="setSelectedColumnOrder($event)"
+    >
+    </sp-field-order-config>
     <sp-split-section [level]="3" [title]="'Settings' | translate">
         <sp-form-field [level]="3" [label]="'Filter' | translate">
             <mat-form-field fxFlex="100">
diff --git a/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.scss b/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.scss
index e926be0642..2bc4d58126 100644
--- a/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.scss
+++ b/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.scss
@@ -16,59 +16,6 @@
  *
  */
 
-.selected-column-list {
-    gap: var(--space-sm);
-}
-
-.selected-column-row {
-    gap: var(--space-sm);
-    padding: var(--space-xs) var(--space-sm);
-    border: 1px solid var(--color-border-subtle, var(--color-bg-3));
-    border-radius: var(--button-border-radius);
-    background: var(--color-bg-1, var(--color-bg-0));
-}
-
-.selected-column-main {
-    min-width: 0;
-    gap: var(--space-xs);
-    flex: 1;
-}
-
-.selected-column-drag-handle {
-    cursor: grab;
-    color: var(--color-secondary-text);
-}
-
-.selected-column-drag-handle:active {
-    cursor: grabbing;
-}
-
-.selected-column-label {
-    min-width: 0;
-    flex: 1;
-    font-size: var(--font-size-sm);
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-}
-
-.selected-column-actions {
-    flex-shrink: 0;
-    gap: var(--space-xxs);
-}
-
-.cdk-drag-preview {
-    box-sizing: border-box;
-    border-radius: var(--button-border-radius);
-    border: 1px solid var(--color-border-subtle, var(--color-bg-3));
-    background: var(--color-bg-1, var(--color-bg-0));
-    box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.18);
-}
-
-.cdk-drag-placeholder {
-    opacity: 0.35;
-}
-
 .highlight-color-list {
     gap: var(--space-sm);
 }
diff --git a/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.ts b/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.ts
index 957db58dfc..6b31df9c59 100644
--- a/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.ts
+++ b/ui/src/app/chart-shared/components/charts/table/config/table-widget-config.component.ts
@@ -39,15 +39,7 @@ import { TranslatePipe } from '@ngx-translate/core';
 import { MatOption, MatSelect } from '@angular/material/select';
 import { ColorPickerDirective } from 'ngx-color-picker';
 import { MatCheckbox } from '@angular/material/checkbox';
-import {
-    CdkDrag,
-    CdkDragHandle,
-    CdkDropList,
-    CdkDragDrop,
-    moveItemInArray,
-} from '@angular/cdk/drag-drop';
-import { MatIcon } from '@angular/material/icon';
-import { MatIconButton } from '@angular/material/button';
+import { FieldOrderConfigComponent } from '../../../chart-config/field-order-config/field-order-config.component';
 
 @Component({
     selector: 'sp-data-explorer-table-widget-config',
@@ -67,11 +59,7 @@ import { MatIconButton } from '@angular/material/button';
         LayoutDirective,
         LayoutAlignDirective,
         MatCheckbox,
-        CdkDropList,
-        CdkDrag,
-        CdkDragHandle,
-        MatIcon,
-        MatIconButton,
+        FieldOrderConfigComponent,
         TranslatePipe,
         FormFieldComponent,
         SpAlertBannerComponent,
@@ -104,41 +92,9 @@ export class TableWidgetConfigComponent extends BaseWidgetConfig<
         this.triggerViewRefresh();
     }
 
-    moveSelectedColumn(fromIndex: number, offset: number): void {
-        const columns = [
-            ...(this.currentlyConfiguredWidget.visualizationConfig
-                .selectedColumns ?? []),
-        ];
-        const targetIndex = fromIndex + offset;
-
-        if (
-            fromIndex < 0 ||
-            targetIndex < 0 ||
-            fromIndex >= columns.length ||
-            targetIndex >= columns.length
-        ) {
-            return;
-        }
-
-        const [movedColumn] = columns.splice(fromIndex, 1);
-        columns.splice(targetIndex, 0, movedColumn);
-        this.currentlyConfiguredWidget.visualizationConfig.selectedColumns =
-            columns;
-        this.triggerViewRefresh();
-    }
-
-    dropSelectedColumn(event: CdkDragDrop<DataExplorerField[]>): void {
-        if (event.previousIndex === event.currentIndex) {
-            return;
-        }
-
-        const columns = [
-            ...(this.currentlyConfiguredWidget.visualizationConfig
-                .selectedColumns ?? []),
-        ];
-        moveItemInArray(columns, event.previousIndex, event.currentIndex);
+    setSelectedColumnOrder(selectedColumns: DataExplorerField[]): void {
         this.currentlyConfiguredWidget.visualizationConfig.selectedColumns =
-            columns;
+            selectedColumns;
         this.triggerViewRefresh();
     }
 
@@ -247,23 +203,6 @@ export class TableWidgetConfigComponent extends BaseWidgetConfig<
         return true;
     }
 
-    canMoveSelectedColumnUp(index: number): boolean {
-        return index > 0;
-    }
-
-    canMoveSelectedColumnDown(index: number): boolean {
-        return (
-            index <
-            (this.currentlyConfiguredWidget.visualizationConfig.selectedColumns
-                ?.length ?? 0) -
-                1
-        );
-    }
-
-    selectedColumnLabel(field: DataExplorerField): string {
-        return `${field.runtimeName} (${field.measure})`;
-    }
-
     private syncHighlightColorMap(): void {
         const activeColorKeys = new Set(
             (
diff --git a/ui/src/app/chart-shared/components/charts/table/table-widget.component.ts b/ui/src/app/chart-shared/components/charts/table/table-widget.component.ts
index 5a176f73f9..a6061a67d6 100644
--- a/ui/src/app/chart-shared/components/charts/table/table-widget.component.ts
+++ b/ui/src/app/chart-shared/components/charts/table/table-widget.component.ts
@@ -46,6 +46,7 @@ import { NoDataInDateRangeComponent } from '../base/no-data/no-data-in-date-rang
 import { TooMuchDataComponent } from '../base/too-much-data/too-much-data.component';
 import { TranslatePipe } from '@ngx-translate/core';
 import { WidgetNumberAppearanceConfig } from '../../../models/dataview-dashboard.model';
+import { ResultLabelService } from '../../../services/result-label.service';
 
 type SortDirection = 'asc' | 'desc' | '';
 
@@ -120,6 +121,7 @@ const TIMESTAMP_MASK = 'yyyy-mm-dd HH:mm:ss.SSS';
 })
 export class TableWidgetComponent extends BaseDataExplorerWidgetDirective<TableWidgetModel> {
     private elRef = inject(ElementRef);
+    private resultLabelService = inject(ResultLabelService);
 
     private static readonly DEFAULT_PAGE_SIZE = 20;
 
@@ -865,8 +867,22 @@ export class TableWidgetComponent extends BaseDataExplorerWidgetDirective<TableW
         return Math.max(0, normalizedIndex - firstReorderableIndex);
     }
 
-    headerLabel = (column: string): string =>
-        column === 'time' ? 'Time' : column;
+    headerLabel = (column: string): string => {
+        if (column === 'time') {
+            return 'Time';
+        }
+
+        const field = this.getFieldByColumnName(column);
+        if (!field) {
+            return column;
+        }
+
+        return this.resultLabelService.resolveLabel(
+            this.dataExplorerWidget.dataConfig.sourceConfigs[field.sourceIndex]
+                .queryConfig,
+            field,
+        );
+    };
 
     formatCellValue(column: string, value: unknown): unknown {
         if (column === 'time') {
@@ -1203,6 +1219,44 @@ export class TableWidgetComponent extends BaseDataExplorerWidgetDirective<TableW
             : (n - stats.min) / (stats.max - stats.min);
     }
 
+    private getFieldByColumnName(
+        column: string,
+    ): DataExplorerField | undefined {
+        const selectedField = (
+            this.dataExplorerWidget.visualizationConfig.selectedColumns ?? []
+        ).find(f => f.fullDbName === column);
+
+        if (selectedField) {
+            return selectedField;
+        }
+
+        for (const [
+            sourceIndex,
+            sourceConfig,
+        ] of this.dataExplorerWidget.dataConfig.sourceConfigs.entries()) {
+            const groupedField = sourceConfig.queryConfig.groupBy
+                ?.filter(groupBy => groupBy.selected)
+                .find(groupBy => groupBy.runtimeName === column);
+
+            if (groupedField) {
+                return {
+                    runtimeName: groupedField.runtimeName,
+                    measure: sourceConfig.measureName,
+                    fullDbName: groupedField.runtimeName,
+                    sourceIndex,
+                    fieldCharacteristics: {
+                        dimension: true,
+                        numeric: false,
+                        binary: false,
+                        semanticTypes: [],
+                    },
+                };
+            }
+        }
+
+        return undefined;
+    }
+
     private toNumber(value: unknown): number | undefined {
         if (typeof value === 'number' && Number.isFinite(value)) return value;
         if (typeof value === 'string' && value.trim() !== '') {
diff --git a/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts b/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts
index ebd417bfa5..b094faedd8 100644
--- a/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts
+++ b/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts
@@ -27,6 +27,7 @@ import { Injectable } from '@angular/core';
 import { TimeSeriesChartWidgetModel } from './model/time-series-chart-widget.model';
 import { DataExplorerField } from '@streampipes/platform-services';
 import { SpBaseEchartsRenderer } from '../../../echarts-renderer/base-echarts-renderer';
+import { ResultLabelService } from '../../../services/result-label.service';
 import {
     GeneratedDataset,
     TagValue,
@@ -44,6 +45,10 @@ import type { FieldUpdateInfo } from '../../../models/field-update.model';
 
 @Injectable({ providedIn: 'root' })
 export class SpTimeseriesRendererService extends SpBaseEchartsRenderer<TimeSeriesChartWidgetModel> {
+    constructor(private resultLabelService: ResultLabelService) {
+        super();
+    }
+
     applyOptions(
         generatedDataset: GeneratedDataset,
         options: EChartsOption,
@@ -71,10 +76,16 @@ export class SpTimeseriesRendererService extends SpBaseEchartsRenderer<TimeSerie
                     const rawDatasetDimensions = dataset.rawDataset.dimensions;
                     const groupIndex = i - dataset.meta.preparedDataStartIndex;
                     const tag = dataset.tagValues[groupIndex];
-                    const displayName =
+                    const legacyDisplayName =
                         widgetConfig.visualizationConfig.displayName[
                             field.fullDbName + field.sourceIndex
                         ];
+                    const displayName = this.resultLabelService.resolveLabel(
+                        widgetConfig.dataConfig.sourceConfigs[field.sourceIndex]
+                            .queryConfig,
+                        field,
+                        legacyDisplayName,
+                    );
                     const mappedGroupLabel = this.colorizationService.findLabel(
                         widgetConfig.visualizationConfig,
                         field,
diff --git a/ui/src/app/chart-shared/components/charts/value-card/appearance-config/value-card-appearance-config.component.html b/ui/src/app/chart-shared/components/charts/value-card/appearance-config/value-card-appearance-config.component.html
new file mode 100644
index 0000000000..1e2c42bd62
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/value-card/appearance-config/value-card-appearance-config.component.html
@@ -0,0 +1,50 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<sp-split-section [level]="3" [title]="'Card settings' | translate">
+    <sp-number-format-config [appearanceConfig]="appearanceConfig">
+    </sp-number-format-config>
+</sp-split-section>
+
+<sp-split-section [level]="3" [title]="'Typography' | translate">
+    <sp-form-field [level]="3" [label]="'Label font size' | translate">
+        <mat-form-field appearance="outline" color="accent" fxFlex="100">
+            <input
+                matInput
+                type="number"
+                min="1"
+                [ngModel]="config.labelFontSize"
+                (ngModelChange)="updateFontSize('labelFontSize', $event)"
+                placeholder="{{ 'Auto' | translate }}"
+            />
+        </mat-form-field>
+    </sp-form-field>
+
+    <sp-form-field [level]="3" [label]="'Value font size' | translate">
+        <mat-form-field appearance="outline" color="accent" fxFlex="100">
+            <input
+                matInput
+                type="number"
+                min="1"
+                [ngModel]="config.valueFontSize"
+                (ngModelChange)="updateFontSize('valueFontSize', $event)"
+                placeholder="{{ 'Auto' | translate }}"
+            />
+        </mat-form-field>
+    </sp-form-field>
+</sp-split-section>
diff --git a/ui/src/app/chart-shared/components/charts/value-card/appearance-config/value-card-appearance-config.component.ts b/ui/src/app/chart-shared/components/charts/value-card/appearance-config/value-card-appearance-config.component.ts
new file mode 100644
index 0000000000..388438ace4
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/value-card/appearance-config/value-card-appearance-config.component.ts
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, Input, OnInit, inject } from '@angular/core';
+import { SplitSectionComponent } from '@streampipes/shared-ui';
+import { TranslatePipe } from '@ngx-translate/core';
+import { SpNumberFormatConfigComponent } from '../../../chart-config/number-format-config/number-format-config.component';
+import { ValueCardAppearanceConfig } from '../model/value-card-widget.model';
+import { FormFieldComponent } from '@streampipes/shared-ui';
+import { MatFormField } from '@angular/material/form-field';
+import { MatInput } from '@angular/material/input';
+import { FormsModule } from '@angular/forms';
+import { FlexDirective } from '@ngbracket/ngx-layout/flex';
+import { ChartConfigurationService } from '../../../../services/chart-configuration.service';
+
+@Component({
+    selector: 'sp-value-card-widget-appearance-config',
+    templateUrl: './value-card-appearance-config.component.html',
+    imports: [
+        SplitSectionComponent,
+        TranslatePipe,
+        SpNumberFormatConfigComponent,
+        FormFieldComponent,
+        MatFormField,
+        MatInput,
+        FormsModule,
+        FlexDirective,
+    ],
+})
+export class ValueCardWidgetAppearanceConfigComponent implements OnInit {
+    private widgetConfigurationService = inject(ChartConfigurationService);
+
+    @Input()
+    appearanceConfig: ValueCardAppearanceConfig;
+
+    ngOnInit(): void {
+        this.ensureAppearanceConfig();
+    }
+
+    get config(): ValueCardAppearanceConfig {
+        return this.ensureAppearanceConfig();
+    }
+
+    updateFontSize(
+        key: 'labelFontSize' | 'valueFontSize',
+        value: number | null,
+    ): void {
+        this.config[key] =
+            value === null || value === undefined || value <= 0
+                ? undefined
+                : value;
+
+        this.widgetConfigurationService.notify({
+            refreshView: true,
+            refreshData: false,
+        });
+    }
+
+    private ensureAppearanceConfig(): ValueCardAppearanceConfig {
+        this.appearanceConfig ??= {
+            backgroundColor: 'var(--color-bg-0)',
+            textColor: 'var(--color-default-text)',
+            widgetTitle: '',
+            numberFormat: {},
+        };
+
+        return this.appearanceConfig;
+    }
+}
diff --git a/ui/src/app/chart-shared/components/charts/value-card/config/value-card-widget-config.component.html b/ui/src/app/chart-shared/components/charts/value-card/config/value-card-widget-config.component.html
new file mode 100644
index 0000000000..6f5acf1961
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/value-card/config/value-card-widget-config.component.html
@@ -0,0 +1,86 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<sp-visualization-config-outer
+    [configurationValid]="
+        currentlyConfiguredWidget.visualizationConfig.configurationValid
+    "
+>
+    <sp-select-properties-config
+        [title]="'Fields' | translate"
+        [availableProperties]="fieldProvider.allFields"
+        [selectedProperties]="
+            currentlyConfiguredWidget.visualizationConfig.selectedFields
+        "
+        (changeSelectedProperties)="updateSelectedFields($event)"
+    >
+    </sp-select-properties-config>
+
+    <sp-field-order-config
+        [sectionTitle]="'Field Order'"
+        [selectedFields]="
+            currentlyConfiguredWidget.visualizationConfig.selectedFields
+        "
+        (selectedFieldsChange)="setSelectedFieldOrder($event)"
+    >
+    </sp-field-order-config>
+
+    <sp-split-section [level]="3" [title]="'Content' | translate">
+        <sp-form-field [level]="3" [label]="'Timestamp' | translate">
+            <mat-checkbox
+                [checked]="
+                    currentlyConfiguredWidget.visualizationConfig.showTimestamp
+                "
+                (change)="
+                    currentlyConfiguredWidget.visualizationConfig.showTimestamp =
+                        $event.checked;
+                    triggerViewRefresh()
+                "
+            >
+                {{ 'Show latest time above card' | translate }}
+            </mat-checkbox>
+        </sp-form-field>
+
+        <sp-form-field [level]="3" [label]="'Title' | translate">
+            <mat-form-field fxFlex="100">
+                <input
+                    matInput
+                    type="text"
+                    [(ngModel)]="
+                        currentlyConfiguredWidget.visualizationConfig.title
+                    "
+                    (ngModelChange)="triggerViewRefresh()"
+                />
+            </mat-form-field>
+        </sp-form-field>
+
+        <sp-form-field [level]="3" [label]="'Description' | translate">
+            <mat-form-field fxFlex="100">
+                <textarea
+                    matInput
+                    rows="3"
+                    [(ngModel)]="
+                        currentlyConfiguredWidget.visualizationConfig
+                            .description
+                    "
+                    (ngModelChange)="triggerViewRefresh()"
+                ></textarea>
+            </mat-form-field>
+        </sp-form-field>
+    </sp-split-section>
+</sp-visualization-config-outer>
diff --git a/ui/src/app/chart-shared/components/charts/value-card/config/value-card-widget-config.component.ts b/ui/src/app/chart-shared/components/charts/value-card/config/value-card-widget-config.component.ts
new file mode 100644
index 0000000000..3fec8dee87
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/value-card/config/value-card-widget-config.component.ts
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component } from '@angular/core';
+import { BaseWidgetConfig } from '../../base/base-widget-config';
+import {
+    ValueCardVisConfig,
+    ValueCardWidgetModel,
+} from '../model/value-card-widget.model';
+import { DataExplorerField } from '@streampipes/platform-services';
+import { SpVisualizationConfigOuterComponent } from '../../../chart-config/visualization-config-outer/visualization-config-outer.component';
+import { SelectMultiplePropertiesConfigComponent } from '../../../chart-config/select-multiple-properties-config/select-multiple-properties-config.component';
+import {
+    FormFieldComponent,
+    SplitSectionComponent,
+} from '@streampipes/shared-ui';
+import { MatFormField } from '@angular/material/form-field';
+import { MatInput } from '@angular/material/input';
+import { FormsModule } from '@angular/forms';
+import { FlexDirective } from '@ngbracket/ngx-layout/flex';
+import { TranslatePipe } from '@ngx-translate/core';
+import { FieldOrderConfigComponent } from '../../../chart-config/field-order-config/field-order-config.component';
+import { MatCheckbox } from '@angular/material/checkbox';
+
+@Component({
+    selector: 'sp-data-explorer-value-card-widget-config',
+    templateUrl: './value-card-widget-config.component.html',
+    imports: [
+        SpVisualizationConfigOuterComponent,
+        SplitSectionComponent,
+        SelectMultiplePropertiesConfigComponent,
+        FormFieldComponent,
+        FieldOrderConfigComponent,
+        MatFormField,
+        MatInput,
+        MatCheckbox,
+        FlexDirective,
+        FormsModule,
+        TranslatePipe,
+    ],
+})
+export class ValueCardWidgetConfigComponent extends BaseWidgetConfig<
+    ValueCardWidgetModel,
+    ValueCardVisConfig
+> {
+    updateSelectedFields(fields: DataExplorerField[]): void {
+        this.currentlyConfiguredWidget.visualizationConfig.selectedFields =
+            this.mergeSelectedFieldOrder(fields);
+        this.triggerViewRefresh();
+    }
+
+    setSelectedFieldOrder(fields: DataExplorerField[]): void {
+        this.currentlyConfiguredWidget.visualizationConfig.selectedFields =
+            fields;
+        this.triggerViewRefresh();
+    }
+
+    protected applyWidgetConfig(config: ValueCardVisConfig): void {
+        config.selectedFields = this.getCompatibleSelectedFields(
+            config.selectedFields,
+        );
+        config.title ??= '';
+        config.description ??= '';
+        config.showTimestamp ??= false;
+    }
+
+    protected requiredFieldsForChartPresent(): boolean {
+        return this.fieldProvider.allFields.length > 0;
+    }
+
+    private getCompatibleSelectedFields(
+        selectedFields?: DataExplorerField[],
+    ): DataExplorerField[] {
+        const availableFields = this.fieldProvider.allFields;
+
+        if (!selectedFields?.length) {
+            return availableFields.slice(
+                0,
+                Math.min(4, availableFields.length),
+            );
+        }
+
+        return selectedFields.filter(selectedField =>
+            availableFields.find(
+                availableField =>
+                    availableField.fullDbName === selectedField.fullDbName &&
+                    availableField.sourceIndex === selectedField.sourceIndex,
+            ),
+        );
+    }
+
+    private mergeSelectedFieldOrder(
+        nextSelectedFields: DataExplorerField[],
+    ): DataExplorerField[] {
+        const currentSelectedFields =
+            this.currentlyConfiguredWidget.visualizationConfig.selectedFields ??
+            [];
+
+        const retainedFields = currentSelectedFields.filter(currentField =>
+            nextSelectedFields.some(nextField =>
+                this.isSameField(currentField, nextField),
+            ),
+        );
+
+        const newlyAddedFields = nextSelectedFields.filter(
+            nextField =>
+                !currentSelectedFields.some(currentField =>
+                    this.isSameField(currentField, nextField),
+                ),
+        );
+
+        return [...retainedFields, ...newlyAddedFields];
+    }
+
+    private isSameField(a: DataExplorerField, b: DataExplorerField): boolean {
+        return a.fullDbName === b.fullDbName && a.sourceIndex === b.sourceIndex;
+    }
+}
diff --git a/ui/src/app/chart-shared/components/charts/value-card/model/value-card-widget.model.ts b/ui/src/app/chart-shared/components/charts/value-card/model/value-card-widget.model.ts
new file mode 100644
index 0000000000..d75be279f3
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/value-card/model/value-card-widget.model.ts
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import {
+    DataExplorerDataConfig,
+    DataExplorerField,
+    DataExplorerWidgetModel,
+} from '@streampipes/platform-services';
+import {
+    DataExplorerVisConfig,
+    WidgetNumberAppearanceConfig,
+} from '../../../../models/dataview-dashboard.model';
+
+export interface ValueCardVisConfig extends DataExplorerVisConfig {
+    selectedFields?: DataExplorerField[];
+    title?: string;
+    description?: string;
+    showTimestamp?: boolean;
+}
+
+export interface ValueCardAppearanceConfig extends WidgetNumberAppearanceConfig {
+    labelFontSize?: number;
+    valueFontSize?: number;
+}
+
+export interface ValueCardWidgetModel extends DataExplorerWidgetModel {
+    dataConfig: DataExplorerDataConfig;
+    visualizationConfig: ValueCardVisConfig;
+    baseAppearanceConfig: ValueCardAppearanceConfig;
+}
diff --git a/ui/src/app/chart-shared/components/charts/value-card/value-card-widget.component.html b/ui/src/app/chart-shared/components/charts/value-card/value-card-widget.component.html
new file mode 100644
index 0000000000..943033f6e4
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/value-card/value-card-widget.component.html
@@ -0,0 +1,91 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<div
+    class="value-card-widget"
+    fxLayout="column"
+    fxFlex="100"
+    [ngStyle]="widgetStyles"
+    data-cy="value-card-widget"
+>
+    @if (showNoDataInDateRange) {
+        <sp-no-data-in-date-range [viewDateRange]="timeSettings" class="h-100">
+        </sp-no-data-in-date-range>
+    }
+
+    @if (showTooMuchData) {
+        <sp-too-much-data
+            [amountOfEvents]="amountOfTooMuchEvents"
+            (loadDataWithTooManyEventsEmitter)="loadDataWithTooManyEvents()"
+            class="h-100"
+        >
+        </sp-too-much-data>
+    }
+
+    @if (showInvalidConfiguration) {
+        <sp-invalid-configuration
+            [widgetTypeLabel]="widgetTypeLabel"
+            class="h-100"
+        >
+        </sp-invalid-configuration>
+    }
+
+    @if (showData) {
+        <div class="value-card-shell" fxLayout="column" fxFlex="100">
+            @if (titleText || descriptionText) {
+                <div class="value-card-copy" fxLayout="column">
+                    @if (titleText) {
+                        <div class="value-card-title">
+                            {{ titleText }}
+                        </div>
+                    }
+
+                    @if (descriptionText) {
+                        <div class="value-card-description">
+                            {{ descriptionText }}
+                        </div>
+                    }
+                </div>
+            }
+
+            @if (showTimestamp) {
+                <div class="value-card-timestamp">
+                    {{ latestTimestampLabel }}
+                </div>
+            }
+
+            <article class="value-card-panel" fxFlex>
+                <div
+                    class="value-card-list"
+                    [style.--value-card-list-columns]="valueListColumns"
+                >
+                    @for (card of valueCards; track trackCard($index, card)) {
+                        <section class="value-card-item">
+                            <div class="value-card-item-label">
+                                {{ card.label }}
+                            </div>
+                            <div class="value-card-item-value">
+                                {{ card.value }}
+                            </div>
+                        </section>
+                    }
+                </div>
+            </article>
+        </div>
+    }
+</div>
diff --git a/ui/src/app/chart-shared/components/charts/value-card/value-card-widget.component.scss b/ui/src/app/chart-shared/components/charts/value-card/value-card-widget.component.scss
new file mode 100644
index 0000000000..703a33ca15
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/value-card/value-card-widget.component.scss
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+:host {
+    display: block;
+    height: 100%;
+}
+
+.h-100 {
+    height: 100%;
+}
+
+.value-card-widget {
+    box-sizing: border-box;
+    height: 100%;
+    min-height: 0;
+    overflow: hidden;
+    padding: var(--value-card-padding);
+}
+
+.value-card-shell {
+    gap: var(--value-card-gap);
+    min-height: 0;
+}
+
+.value-card-copy {
+    gap: calc(var(--value-card-gap) * 0.45);
+}
+
+.value-card-title {
+    font-size: var(--value-card-title-size);
+    font-weight: 700;
+    letter-spacing: -0.03em;
+    line-height: 1.08;
+}
+
+.value-card-description {
+    color: var(--value-card-muted-color);
+    font-size: var(--value-card-description-size);
+    line-height: 1.35;
+    max-width: 48ch;
+}
+
+.value-card-timestamp {
+    color: var(--value-card-muted-color);
+    font-size: 0.82rem;
+    font-weight: 600;
+    letter-spacing: 0.02em;
+    line-height: 1.2;
+}
+
+.value-card-panel {
+    background: var(--value-card-panel-background);
+    border: 1px solid var(--value-card-border-color);
+    border-radius: 5px;
+    min-height: 0;
+    overflow: hidden;
+    padding: 0 1.2rem;
+}
+
+.value-card-list {
+    display: grid;
+    align-content: start;
+    gap: 0;
+    grid-template-columns: repeat(
+        var(--value-card-list-columns),
+        minmax(0, 1fr)
+    );
+    min-height: 0;
+}
+
+.value-card-item {
+    align-items: start;
+    border-bottom: 1px solid var(--value-card-divider-color);
+    display: flex;
+    flex-direction: column;
+    gap: var(--value-card-row-gap);
+    min-height: 0;
+    padding: var(--value-card-row-padding) 0;
+}
+
+.value-card-item-label {
+    color: var(--value-card-muted-color);
+    font-size: var(--value-card-label-size);
+    font-weight: 600;
+    line-height: 1.3;
+    min-width: 0;
+    overflow-wrap: anywhere;
+}
+
+.value-card-item-value {
+    font-size: var(--value-card-value-size);
+    font-weight: 700;
+    letter-spacing: -0.04em;
+    line-height: 0.98;
+    min-width: 0;
+    overflow-wrap: anywhere;
+    text-align: left;
+}
+
+.value-card-item:last-child {
+    border-bottom: 0;
+}
diff --git a/ui/src/app/chart-shared/components/charts/value-card/value-card-widget.component.ts b/ui/src/app/chart-shared/components/charts/value-card/value-card-widget.component.ts
new file mode 100644
index 0000000000..380ce0dad6
--- /dev/null
+++ b/ui/src/app/chart-shared/components/charts/value-card/value-card-widget.component.ts
@@ -0,0 +1,371 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { NgStyle } from '@angular/common';
+import { Component, LOCALE_ID, OnInit, inject } from '@angular/core';
+import {
+    DataExplorerField,
+    SpQueryResult,
+} from '@streampipes/platform-services';
+import { FlexDirective, LayoutDirective } from '@ngbracket/ngx-layout/flex';
+import { BaseDataExplorerWidgetDirective } from '../base/base-data-explorer-widget.directive';
+import { NoDataInDateRangeComponent } from '../base/no-data/no-data-in-date-range.component';
+import { TooMuchDataComponent } from '../base/too-much-data/too-much-data.component';
+import { SpInvalidConfigurationComponent } from '../base/invalid-configuration/invalid-configuration.component';
+import { ChartRegistry } from '../../../registry/chart-registry.service';
+import {
+    ValueCardAppearanceConfig,
+    ValueCardVisConfig,
+    ValueCardWidgetModel,
+} from './model/value-card-widget.model';
+import { ResultLabelService } from '../../../services/result-label.service';
+import {
+    clampValue,
+    formatWidgetNumber,
+    resolveResponsiveFontSize,
+    scaleResponsiveValue,
+} from '../../../services/widget-render.utils';
+
+interface ValueCardView {
+    id: string;
+    label: string;
+    value: string;
+}
+
+@Component({
+    selector: 'sp-data-explorer-value-card-widget',
+    templateUrl: './value-card-widget.component.html',
+    styleUrls: ['./value-card-widget.component.scss'],
+    imports: [
+        LayoutDirective,
+        FlexDirective,
+        NgStyle,
+        NoDataInDateRangeComponent,
+        TooMuchDataComponent,
+        SpInvalidConfigurationComponent,
+    ],
+})
+export class ValueCardWidgetComponent
+    extends BaseDataExplorerWidgetDirective<ValueCardWidgetModel>
+    implements OnInit
+{
+    valueCards: ValueCardView[] = [];
+    widgetTypeLabel: string;
+    latestTimestampLabel?: string;
+
+    private hasReceivedData = false;
+    private latestData: SpQueryResult[] = [];
+    private readonly locale = inject(LOCALE_ID);
+    private readonly chartRegistry = inject(ChartRegistry);
+    private readonly resultLabelService = inject(ResultLabelService);
+
+    ngOnInit(): void {
+        super.ngOnInit();
+        this.widgetTypeLabel = this.chartRegistry.getChartTemplate(
+            this.dataExplorerWidget.widgetType,
+        ).label;
+    }
+
+    get appearanceConfig(): ValueCardAppearanceConfig {
+        this.dataExplorerWidget.baseAppearanceConfig ??= {
+            backgroundColor: 'var(--color-bg-0)',
+            textColor: 'var(--color-default-text)',
+            widgetTitle: '',
+            numberFormat: {},
+        };
+
+        return this.dataExplorerWidget
+            .baseAppearanceConfig as ValueCardAppearanceConfig;
+    }
+
+    get widgetStyles(): Record<string, string> {
+        const compactMode =
+            (this.currentWidth ?? 0) < 640 || this.hasManyValues;
+        const fontScale = this.getFontScale();
+
+        return {
+            'background': this.appearanceConfig.backgroundColor,
+            'color': this.appearanceConfig.textColor,
+            '--value-card-padding': compactMode ? '14px' : '20px',
+            '--value-card-gap': compactMode ? '12px' : '18px',
+            '--value-card-title-size': compactMode ? '18px' : '24px',
+            '--value-card-description-size': compactMode ? '12px' : '14px',
+            '--value-card-row-padding': `${scaleResponsiveValue(
+                12,
+                22,
+                fontScale,
+                0.7,
+                1.45,
+            )}px`,
+            '--value-card-label-size': `${resolveResponsiveFontSize(
+                this.appearanceConfig.labelFontSize,
+                11,
+                16,
+                fontScale,
+                0.7,
+                1.45,
+            )}px`,
+            '--value-card-value-size': `${resolveResponsiveFontSize(
+                this.appearanceConfig.valueFontSize,
+                20,
+                42,
+                fontScale,
+                0.7,
+                1.45,
+            )}px`,
+            '--value-card-row-gap': `${scaleResponsiveValue(
+                4,
+                10,
+                fontScale,
+                0.7,
+                1.45,
+            )}px`,
+            '--value-card-border-color':
+                'color-mix(in srgb, currentColor 10%, transparent)',
+            '--value-card-divider-color':
+                'color-mix(in srgb, currentColor 8%, transparent)',
+            '--value-card-muted-color':
+                'color-mix(in srgb, currentColor 68%, transparent)',
+            '--value-card-panel-background':
+                'color-mix(in srgb, currentColor 6%, transparent)',
+        };
+    }
+
+    get content(): ValueCardVisConfig {
+        return this.dataExplorerWidget.visualizationConfig;
+    }
+
+    get titleText(): string {
+        return this.content.title?.trim() ?? '';
+    }
+
+    get descriptionText(): string {
+        return this.content.description?.trim() ?? '';
+    }
+
+    get showTimestamp(): boolean {
+        return !!this.content.showTimestamp;
+    }
+
+    get hasManyValues(): boolean {
+        return this.valueCards.length > 3;
+    }
+
+    beforeDataFetched(): void {
+        if (this.valueCards.length > 0) {
+            this.setShownComponents(false, true, false, false);
+            return;
+        }
+
+        this.setShownComponents(false, false, true, false);
+    }
+
+    onDataReceived(spQueryResults: SpQueryResult[]): void {
+        this.hasReceivedData = true;
+        this.latestData = spQueryResults;
+        this.refreshView();
+    }
+
+    onResize(_width: number, _height: number): void {
+        this.refreshView();
+    }
+
+    refreshView(): void {
+        this.updateCards();
+    }
+
+    handleUpdatedFields(
+        addedFields: DataExplorerField[],
+        removedFields: DataExplorerField[],
+    ): void {
+        const currentSelection = this.content.selectedFields ?? [];
+        this.content.selectedFields = currentSelection
+            .filter(
+                field =>
+                    !removedFields.find(
+                        removedField =>
+                            removedField.fullDbName === field.fullDbName &&
+                            removedField.sourceIndex === field.sourceIndex,
+                    ),
+            )
+            .concat(
+                addedFields.filter(
+                    field =>
+                        !currentSelection.find(
+                            currentField =>
+                                currentField.fullDbName === field.fullDbName &&
+                                currentField.sourceIndex === field.sourceIndex,
+                        ),
+                ),
+            );
+        this.refreshView();
+    }
+
+    trackCard(_index: number, card: ValueCardView): string {
+        return card.id;
+    }
+
+    private updateCards(): void {
+        if (this.content.configurationValid === false) {
+            this.showInvalidConfiguration = true;
+            this.valueCards = [];
+            this.setShownComponents(false, false, false, false);
+            return;
+        }
+
+        this.showInvalidConfiguration = false;
+        if (!this.hasReceivedData && this.latestData.length === 0) {
+            return;
+        }
+
+        this.latestTimestampLabel = this.getLatestTimestampLabel();
+        const selectedFields = this.content.selectedFields ?? [];
+        this.valueCards = selectedFields
+            .map(field => this.makeCard(field))
+            .filter((card): card is ValueCardView => !!card);
+
+        if (this.valueCards.length === 0) {
+            this.setShownComponents(true, false, false, false);
+            return;
+        }
+
+        this.setShownComponents(false, true, false, false);
+    }
+
+    private makeCard(field: DataExplorerField): ValueCardView | undefined {
+        const sourceResult = this.latestData[field.sourceIndex];
+
+        if (!sourceResult?.headers?.length) {
+            return undefined;
+        }
+
+        const fieldIndex = sourceResult.headers.findIndex(
+            header => header === field.fullDbName,
+        );
+
+        if (fieldIndex < 0) {
+            return undefined;
+        }
+
+        const firstRow = this.getFirstRow(sourceResult);
+        if (!firstRow) {
+            return undefined;
+        }
+
+        const queryConfig =
+            this.dataExplorerWidget.dataConfig.sourceConfigs[field.sourceIndex]
+                ?.queryConfig;
+
+        return {
+            id: `${field.sourceIndex}-${field.fullDbName}`,
+            label: queryConfig
+                ? this.resultLabelService.resolveLabel(queryConfig, field)
+                : (field.runtimeName ?? field.fullDbName),
+            value: this.formatValue(firstRow[fieldIndex]),
+        };
+    }
+
+    private getFirstRow(queryResult: SpQueryResult): unknown[] | undefined {
+        return queryResult.allDataSeries.find(series => series.rows.length > 0)
+            ?.rows[0];
+    }
+
+    private formatValue(value: unknown): string {
+        if (value === null || value === undefined || value === '') {
+            return '-';
+        }
+
+        if (typeof value === 'number' && Number.isFinite(value)) {
+            return formatWidgetNumber(
+                value,
+                this.locale,
+                this.appearanceConfig.numberFormat?.decimals,
+            );
+        }
+
+        if (typeof value === 'boolean') {
+            return value ? 'True' : 'False';
+        }
+
+        return String(value);
+    }
+
+    private getLatestTimestampLabel(): string | undefined {
+        const firstQueryResult = this.latestData.find(
+            queryResult => !!this.getFirstRow(queryResult),
+        );
+
+        if (!firstQueryResult) {
+            return undefined;
+        }
+
+        const firstTimestamp = this.getFirstTimestamp(firstQueryResult);
+        if (firstTimestamp === undefined) {
+            return undefined;
+        }
+
+        return new Intl.DateTimeFormat(this.locale, {
+            dateStyle: 'medium',
+            timeStyle: 'medium',
+        }).format(new Date(firstTimestamp));
+    }
+
+    private getFirstTimestamp(queryResult: SpQueryResult): number | undefined {
+        const timeIndex = queryResult.headers.findIndex(
+            header => header === 'time',
+        );
+
+        if (timeIndex < 0) {
+            return undefined;
+        }
+
+        const firstRow = this.getFirstRow(queryResult);
+        if (!firstRow) {
+            return undefined;
+        }
+
+        const firstTimestamp = new Date(
+            firstRow[timeIndex] as string,
+        ).getTime();
+        return Number.isFinite(firstTimestamp) ? firstTimestamp : undefined;
+    }
+
+    private getFontScale(): number {
+        const width = this.currentWidth ?? 0;
+        const height = this.currentHeight ?? 0;
+        const rowCount = Math.max(this.valueCards.length, 1);
+
+        if (width <= 0 || height <= 0) {
+            return 0.8;
+        }
+
+        const widthScale = clampValue(width / 640, 0.7, 1.45);
+        const availableHeight = Math.max(height - 110, 120);
+        const heightPerRowScale = clampValue(
+            availableHeight / (rowCount * 84),
+            0.7,
+            1.45,
+        );
+
+        return clampValue(Math.min(widthScale, heightPerRowScale), 0.7, 1.45);
+    }
+
+    get valueListColumns(): number {
+        return 1;
+    }
+}
diff --git a/ui/src/app/chart-shared/registry/chart-registry.service.ts b/ui/src/app/chart-shared/registry/chart-registry.service.ts
index 87165d5c2f..d1d32dd65d 100644
--- a/ui/src/app/chart-shared/registry/chart-registry.service.ts
+++ b/ui/src/app/chart-shared/registry/chart-registry.service.ts
@@ -61,6 +61,12 @@ import { StatusWidgetConfigComponent } from '../components/charts/status/config/
 import { StatusWidgetComponent } from '../components/charts/status/status-widget.component';
 import { IndicatorWidgetComponent } from '../components/charts/indicator/indicator-widget.component';
 import { TranslateService } from '@ngx-translate/core';
+import { ValueCardWidgetComponent } from '../components/charts/value-card/value-card-widget.component';
+import { ValueCardWidgetConfigComponent } from '../components/charts/value-card/config/value-card-widget-config.component';
+import { ValueCardWidgetAppearanceConfigComponent } from '../components/charts/value-card/appearance-config/value-card-appearance-config.component';
+import { ProgressBarWidgetComponent } from '../components/charts/progress-bar/progress-bar-widget.component';
+import { ProgressBarWidgetConfigComponent } from '../components/charts/progress-bar/config/progress-bar-widget-config.component';
+import { ProgressBarWidgetAppearanceConfigComponent } from '../components/charts/progress-bar/appearance-config/progress-bar-appearance-config.component';
 
 @Injectable({ providedIn: 'root' })
 export class ChartRegistry {
@@ -185,6 +191,30 @@ export class ChartRegistry {
                 icon: 'image',
                 description: this.translateService.instant('Display an image'),
             },
+            {
+                id: 'value-card',
+                label: this.translateService.instant('Value Card'),
+                widgetAppearanceConfigurationComponent:
+                    ValueCardWidgetAppearanceConfigComponent,
+                widgetConfigurationComponent: ValueCardWidgetConfigComponent,
+                widgetComponent: ValueCardWidgetComponent,
+                icon: 'view_agenda',
+                description: this.translateService.instant(
+                    'Display the latest values of multiple selected fields in a single card',
+                ),
+            },
+            {
+                id: 'progress-bar',
+                label: this.translateService.instant('Progress Bar'),
+                widgetAppearanceConfigurationComponent:
+                    ProgressBarWidgetAppearanceConfigComponent,
+                widgetConfigurationComponent: ProgressBarWidgetConfigComponent,
+                widgetComponent: ProgressBarWidgetComponent,
+                icon: 'linear_scale',
+                description: this.translateService.instant(
+                    'Display the progress of a numeric value against a target',
+                ),
+            },
             {
                 id: 'indicator-chart',
                 label: this.translateService.instant('Indicator'),
diff --git a/ui/src/app/chart-shared/services/result-label.service.ts b/ui/src/app/chart-shared/services/result-label.service.ts
new file mode 100644
index 0000000000..875ebe2b94
--- /dev/null
+++ b/ui/src/app/chart-shared/services/result-label.service.ts
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Injectable } from '@angular/core';
+import { DataExplorerField, QueryConfig } from '@streampipes/platform-services';
+
+@Injectable({ providedIn: 'root' })
+export class ResultLabelService {
+    makeFieldKey(field: Pick<DataExplorerField, 'fullDbName' | 'sourceIndex'>) {
+        return `${field.fullDbName}:${field.sourceIndex}`;
+    }
+
+    getOverrides(queryConfig: QueryConfig): Record<string, string> {
+        queryConfig.resultLabelOverrides ??= {};
+        return queryConfig.resultLabelOverrides;
+    }
+
+    getOverride(
+        queryConfig: QueryConfig,
+        field: Pick<DataExplorerField, 'fullDbName' | 'sourceIndex'>,
+    ): string | undefined {
+        return this.getOverrides(queryConfig)[this.makeFieldKey(field)];
+    }
+
+    resolveLabel(
+        queryConfig: QueryConfig,
+        field: Pick<DataExplorerField, 'fullDbName' | 'sourceIndex'>,
+        legacyLabel?: string,
+    ): string {
+        return (
+            this.getOverride(queryConfig, field) ??
+            legacyLabel ??
+            field.fullDbName
+        );
+    }
+
+    setOverride(
+        queryConfig: QueryConfig,
+        field: Pick<DataExplorerField, 'fullDbName' | 'sourceIndex'>,
+        label: string,
+        fallbackLabel?: string,
+    ): void {
+        const normalizedLabel = label.trim();
+        const fallback = fallbackLabel ?? field.fullDbName;
+        const overrides = this.getOverrides(queryConfig);
+        const key = this.makeFieldKey(field);
+
+        if (!normalizedLabel || normalizedLabel === fallback) {
+            delete overrides[key];
+            return;
+        }
+
+        overrides[key] = normalizedLabel;
+    }
+}
diff --git a/ui/src/app/chart-shared/services/widget-render.utils.ts b/ui/src/app/chart-shared/services/widget-render.utils.ts
new file mode 100644
index 0000000000..02d8acb9dc
--- /dev/null
+++ b/ui/src/app/chart-shared/services/widget-render.utils.ts
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+export function clampValue(value: number, min: number, max: number): number {
+    return Math.min(Math.max(value, min), max);
+}
+
+export function normalizeWidgetDecimals(decimals: unknown): number | undefined {
+    if (decimals === null || decimals === undefined || decimals === '') {
+        return undefined;
+    }
+
+    const parsedValue = Number(decimals);
+    if (!Number.isFinite(parsedValue)) {
+        return undefined;
+    }
+
+    return Math.min(10, Math.max(0, Math.round(parsedValue)));
+}
+
+export function formatWidgetNumber(
+    value: number,
+    locale: string,
+    decimals: unknown,
+): string {
+    const normalizedDecimals = normalizeWidgetDecimals(decimals);
+
+    return new Intl.NumberFormat(locale, {
+        maximumFractionDigits: normalizedDecimals ?? 20,
+        minimumFractionDigits: normalizedDecimals ?? 0,
+    }).format(value);
+}
+
+export function scaleResponsiveValue(
+    min: number,
+    max: number,
+    scale: number,
+    scaleMin: number,
+    scaleMax: number,
+): number {
+    const normalizedScale = (scale - scaleMin) / (scaleMax - scaleMin);
+    const scaledValue = min + (max - min) * normalizedScale;
+    return Math.round(clampValue(scaledValue, min, max));
+}
+
+export function resolveResponsiveFontSize(
+    manualSize: number | undefined,
+    min: number,
+    max: number,
+    scale: number,
+    scaleMin: number,
+    scaleMax: number,
+): number {
+    if (
+        manualSize === undefined ||
+        manualSize === null ||
+        Number.isNaN(Number(manualSize)) ||
+        manualSize <= 0
+    ) {
+        return scaleResponsiveValue(min, max, scale, scaleMin, scaleMax);
+    }
+
+    return Math.round(Math.max(1, manualSize) * scale * 10) / 10;
+}
diff --git a/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.html b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.html
index af89a9f414..bb64ea35d8 100644
--- a/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.html
+++ b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.html
@@ -410,6 +410,11 @@
                                 #groupSelectionPanel
                                 [sourceConfig]="sourceConfig"
                             ></sp-group-selection-panel>
+                            <sp-result-label-configuration
+                                [sourceConfig]="sourceConfig"
+                                [sourceIndex]="0"
+                            >
+                            </sp-result-label-configuration>
                             <sp-order-selection-panel
                                 [sourceConfig]="sourceConfig"
                             >
diff --git a/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.ts b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.ts
index 5502cff7be..2875af0354 100644
--- a/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.ts
+++ b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/chart-data-settings.component.ts
@@ -77,6 +77,7 @@ import { AggregateConfigurationComponent } from './aggregate-configuration/aggre
 import { FillConfigurationComponent } from './fill-configuration/fill-configuration.component';
 import { FilterSelectionPanelComponent } from './filter-selection-panel/filter-selection-panel.component';
 import { OrderSelectionPanelComponent } from './order-selection-panel/order-selection-panel.component';
+import { ResultLabelConfigurationComponent } from './result-label-configuration/result-label-configuration.component';
 import { TranslatePipe } from '@ngx-translate/core';
 import {
     MatAutocomplete,
@@ -122,6 +123,7 @@ import {
         FilterSelectionPanelComponent,
         GroupSelectionPanelComponent,
         OrderSelectionPanelComponent,
+        ResultLabelConfigurationComponent,
         TranslatePipe,
     ],
 })
@@ -330,6 +332,7 @@ export class ChartDataSettingsComponent implements OnInit {
             measureName,
             queryConfig: {
                 selectedFilters: [],
+                resultLabelOverrides: {},
                 limit: 100,
                 page: 1,
                 aggregationTimeUnit: 'd',
diff --git a/ui/src/app/chart/components/chart-view/designer-panel/data-settings/result-label-configuration/result-label-configuration.component.html b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/result-label-configuration/result-label-configuration.component.html
new file mode 100644
index 0000000000..f4dbb538c0
--- /dev/null
+++ b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/result-label-configuration/result-label-configuration.component.html
@@ -0,0 +1,49 @@
+<!--
+~ Licensed to the Apache Software Foundation (ASF) under one or more
+~ contributor license agreements.  See the NOTICE file distributed with
+~ this work for additional information regarding copyright ownership.
+~ The ASF licenses this file to You under the Apache License, Version 2.0
+~ (the "License"); you may not use this file except in compliance with
+~ the License.  You may obtain a copy of the License at
+~
+~    http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+~
+-->
+
+@if (selectedResultFields.length > 0) {
+    <sp-split-section [level]="3" [title]="'Result Labels' | translate">
+        <div fxLayout="column" fxLayoutGap="10px">
+            @for (field of selectedResultFields; track field.fullDbName) {
+                <div
+                    fxLayout="row"
+                    fxLayoutAlign="start center"
+                    fxLayoutGap="10px"
+                >
+                    <div fxFlex="35" class="text-xs">
+                        {{ field.fullDbName }}
+                    </div>
+                    <div fxFlex="65">
+                        <mat-form-field
+                            appearance="outline"
+                            color="accent"
+                            class="w-100 form-field-small"
+                        >
+                            <input
+                                matInput
+                                [ngModel]="getLabel(field)"
+                                (ngModelChange)="updateLabel(field, $event)"
+                                [placeholder]="field.fullDbName"
+                            />
+                        </mat-form-field>
+                    </div>
+                </div>
+            }
+        </div>
+    </sp-split-section>
+}
diff --git a/ui/src/app/chart/components/chart-view/designer-panel/data-settings/result-label-configuration/result-label-configuration.component.scss b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/result-label-configuration/result-label-configuration.component.scss
new file mode 100644
index 0000000000..13cbc4aacb
--- /dev/null
+++ b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/result-label-configuration/result-label-configuration.component.scss
@@ -0,0 +1,17 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
diff --git a/ui/src/app/chart/components/chart-view/designer-panel/data-settings/result-label-configuration/result-label-configuration.component.ts b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/result-label-configuration/result-label-configuration.component.ts
new file mode 100644
index 0000000000..9bb57c0ce2
--- /dev/null
+++ b/ui/src/app/chart/components/chart-view/designer-panel/data-settings/result-label-configuration/result-label-configuration.component.ts
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, Input, inject } from '@angular/core';
+import {
+    DataExplorerField,
+    SourceConfig,
+} from '@streampipes/platform-services';
+import { ChartFieldProviderService } from '../../../../../../chart-shared/services/chart-field-provider.service';
+import { ResultLabelService } from '../../../../../../chart-shared/services/result-label.service';
+import { ChartConfigurationService } from '../../../../../../chart-shared/services/chart-configuration.service';
+import { SplitSectionComponent } from '@streampipes/shared-ui';
+import {
+    FlexDirective,
+    LayoutAlignDirective,
+    LayoutDirective,
+    LayoutGapDirective,
+} from '@ngbracket/ngx-layout/flex';
+import { MatFormField } from '@angular/material/form-field';
+import { MatInput } from '@angular/material/input';
+import { FormsModule } from '@angular/forms';
+import { TranslatePipe } from '@ngx-translate/core';
+
+@Component({
+    selector: 'sp-result-label-configuration',
+    templateUrl: './result-label-configuration.component.html',
+    styleUrls: ['./result-label-configuration.component.scss'],
+    imports: [
+        SplitSectionComponent,
+        LayoutDirective,
+        LayoutAlignDirective,
+        LayoutGapDirective,
+        FlexDirective,
+        MatFormField,
+        MatInput,
+        FormsModule,
+        TranslatePipe,
+    ],
+})
+export class ResultLabelConfigurationComponent {
+    private fieldProvider = inject(ChartFieldProviderService);
+    private resultLabelService = inject(ResultLabelService);
+    private widgetConfigService = inject(ChartConfigurationService);
+
+    @Input() sourceConfig: SourceConfig;
+    @Input() sourceIndex = 0;
+
+    get selectedResultFields(): DataExplorerField[] {
+        if (!this.sourceConfig?.measure) {
+            return [];
+        }
+
+        return this.fieldProvider
+            .generateFieldLists([this.sourceConfig])
+            .allFields.sort((a, b) => a.fullDbName.localeCompare(b.fullDbName));
+    }
+
+    getLabel(field: DataExplorerField): string {
+        return this.resultLabelService.resolveLabel(
+            this.sourceConfig.queryConfig,
+            { ...field, sourceIndex: this.sourceIndex },
+        );
+    }
+
+    updateLabel(field: DataExplorerField, label: string): void {
+        this.resultLabelService.setOverride(
+            this.sourceConfig.queryConfig,
+            { ...field, sourceIndex: this.sourceIndex },
+            label,
+            field.fullDbName,
+        );
+
+        this.widgetConfigService.notify({
+            refreshData: false,
+            refreshView: true,
+        });
+    }
+}