From 5b6e0e3c3eae5567d72a2cb1ff71672ea536a6b0 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Tue, 6 Jun 2023 22:16:21 +0200 Subject: [PATCH 01/34] Introduce new sensor chart type Signed-off-by: F.N. Claessen --- flexmeasures/api/dev/sensors.py | 1 + .../data/models/charts/belief_charts.py | 98 +++++++++++++++++++ flexmeasures/ui/templates/base.html | 2 +- 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/flexmeasures/api/dev/sensors.py b/flexmeasures/api/dev/sensors.py index 346ebfca9..834d2d5c4 100644 --- a/flexmeasures/api/dev/sensors.py +++ b/flexmeasures/api/dev/sensors.py @@ -45,6 +45,7 @@ class SensorAPI(FlaskView): "include_asset_annotations": fields.Boolean(required=False), "include_account_annotations": fields.Boolean(required=False), "dataset_name": fields.Str(required=False), + "chart_type": fields.Str(required=False), "height": fields.Str(required=False), "width": fields.Str(required=False), }, diff --git a/flexmeasures/data/models/charts/belief_charts.py b/flexmeasures/data/models/charts/belief_charts.py index 09782470e..a9f12d340 100644 --- a/flexmeasures/data/models/charts/belief_charts.py +++ b/flexmeasures/data/models/charts/belief_charts.py @@ -83,6 +83,104 @@ def bar_chart( return chart_specs +def matrix_chart( + sensor: "Sensor", # noqa F821 + event_starts_after: datetime | None = None, + event_ends_before: datetime | None = None, + **override_chart_specs: dict, +): + unit = sensor.unit if sensor.unit else "a.u." + event_value_field_definition = dict( + title=f"{capitalize(sensor.sensor_type)} ({unit})", + format=[".3~r", unit], + formatType="quantityWithUnitFormat", + stack=None, + **FIELD_DEFINITIONS["event_value"], + scale={"scheme": "purplegreen", "domainMid": 0}, + ) + event_start_field_definition = dict( + field="event_start", + type="temporal", + title=None, + axis={ + "labelExpr": "timeFormat(datum.value, '%H:%M')", + "labelOverlap": True, + "labelSeparation": 1, + }, + ) + event_start_date_field_definition = event_start_field_definition.copy() + event_start_field_definition["timeUnit"] = { + "unit": "hoursminutesseconds", + "step": sensor.event_resolution.total_seconds(), + } + event_start_date_field_definition["timeUnit"] = { + "unit": "yearmonthdate", + } + if event_starts_after and event_ends_before: + event_start_date_field_definition["scale"] = { + "domain": [ + event_starts_after.timestamp() * 10**3, + event_ends_before.timestamp() * 10**3 + - 1, # prevent showing next date outside selected daterange + ], + } + event_start_date_field_definition["axis"] = { + "tickCount": "day", + # it's not trivial to center align the labels (vega-lite is missing timeband functionality) + "labelBaseline": "line-bottom", + } + event_start_field_definition["scale"] = { + "domain": [ + {"hours": 0}, + {"hours": 24}, + ] + } + chart_specs = { + "description": "A simple heatmap chart showing sensor data.", + # the sensor type is already shown as the y-axis title (avoid redundant info) + "title": capitalize(sensor.name) if sensor.name != sensor.sensor_type else None, + "layer": [ + { + "mark": { + "type": "rect", + "clip": True, + }, + "encoding": { + "x": event_start_field_definition, + "y": event_start_date_field_definition, + # "color": FIELD_DEFINITIONS["source_name"], + "color": event_value_field_definition, + "detail": FIELD_DEFINITIONS["source"], + "opacity": {"value": 0.7}, + "tooltip": [ + FIELD_DEFINITIONS["full_date"], + { + **event_value_field_definition, + **dict(title=f"{capitalize(sensor.sensor_type)}"), + }, + FIELD_DEFINITIONS["source_name_and_id"], + FIELD_DEFINITIONS["source_model"], + ], + }, + "transform": [ + { + "calculate": "datum.source.name + ' (ID: ' + datum.source.id + ')'", + "as": "source_name_and_id", + }, + ], + }, + REPLAY_RULER, + ], + } + for k, v in override_chart_specs.items(): + chart_specs[k] = v + chart_specs["config"] = { + "legend": {"orient": "right"}, + # "legend": {"direction": "horizontal"}, + } + return chart_specs + + def chart_for_multiple_sensors( sensors_to_show: list["Sensor", list["Sensor"]], # noqa F821 event_starts_after: datetime | None = None, diff --git a/flexmeasures/ui/templates/base.html b/flexmeasures/ui/templates/base.html index edbf456ee..bdfc8c99b 100644 --- a/flexmeasures/ui/templates/base.html +++ b/flexmeasures/ui/templates/base.html @@ -247,7 +247,7 @@ async function embedAndLoad(chartSpecsPath, elementId, datasetName, previousResult, startDate, endDate) { - await vegaEmbed('#'+elementId, chartSpecsPath + 'dataset_name=' + datasetName + '&width=container&include_sensor_annotations=false&include_asset_annotations=false', {{ chart_options | safe }}) + await vegaEmbed('#'+elementId, chartSpecsPath + 'dataset_name=' + datasetName + '&width=container&include_sensor_annotations=false&include_asset_annotations=false&chart_type=matrix_chart', {{ chart_options | safe }}) .then(function (result) { // result.view is the Vega View, chartSpecsPath is the original Vega-Lite specification vegaView = result.view; From 2239ff7a36ebb3a66289f656a0ef27b9b9566114 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Wed, 7 Jun 2023 11:25:24 +0200 Subject: [PATCH 02/34] Fix alignment of y-axis labels Signed-off-by: F.N. Claessen --- flexmeasures/data/models/charts/belief_charts.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flexmeasures/data/models/charts/belief_charts.py b/flexmeasures/data/models/charts/belief_charts.py index a9f12d340..a34a4011a 100644 --- a/flexmeasures/data/models/charts/belief_charts.py +++ b/flexmeasures/data/models/charts/belief_charts.py @@ -126,8 +126,10 @@ def matrix_chart( } event_start_date_field_definition["axis"] = { "tickCount": "day", - # it's not trivial to center align the labels (vega-lite is missing timeband functionality) - "labelBaseline": "line-bottom", + # Center align the date labels + "labelOffset": { + "expr": "(scale('y', 24 * 60 * 60 * 1000) - scale('y', 0)) / 2" + }, } event_start_field_definition["scale"] = { "domain": [ From bfa52abe207bf11ea13c8dd8a39a5171c8163b3a Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Wed, 7 Jun 2023 13:25:13 +0200 Subject: [PATCH 03/34] Add button to switch between sensor chart types Signed-off-by: F.N. Claessen --- .../data/models/charts/belief_charts.py | 2 +- flexmeasures/ui/static/css/flexmeasures.css | 24 +++++++++---- flexmeasures/ui/templates/base.html | 34 ++++++++++++++++++- flexmeasures/ui/templates/views/sensors.html | 9 +++++ 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/flexmeasures/data/models/charts/belief_charts.py b/flexmeasures/data/models/charts/belief_charts.py index a34a4011a..024330b3d 100644 --- a/flexmeasures/data/models/charts/belief_charts.py +++ b/flexmeasures/data/models/charts/belief_charts.py @@ -96,7 +96,7 @@ def matrix_chart( formatType="quantityWithUnitFormat", stack=None, **FIELD_DEFINITIONS["event_value"], - scale={"scheme": "purplegreen", "domainMid": 0}, + scale={"scheme": "blueorange", "domainMid": 0}, ) event_start_field_definition = dict( field="event_start", diff --git a/flexmeasures/ui/static/css/flexmeasures.css b/flexmeasures/ui/static/css/flexmeasures.css index cd42d220a..3dddde6fe 100644 --- a/flexmeasures/ui/static/css/flexmeasures.css +++ b/flexmeasures/ui/static/css/flexmeasures.css @@ -238,7 +238,7 @@ p.error { border: none; } -.navbar-default .dropdown-menu { +.dropdown-menu { border: none !important; } @@ -304,7 +304,7 @@ p.error { padding-bottom: 20px; } - .navbar-default .dropdown-menu a { + .dropdown-menu a { transition: .3s; -webkit-transition: .3s; -moz-transition: .3s; @@ -315,7 +315,7 @@ p.error { } @media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu>li>a { + .dropdown-menu>li>a { color: var(--nav-default-color); transition: .4s; -webkit-transition: .4s; @@ -325,13 +325,23 @@ p.error { } } -.navbar-default .navbar-nav .open .dropdown-menu, -.navbar-default .navbar-nav .open .dropdown-menu>li>a { +.dropdown-menu.center-aligned { + right: auto; + left: 50%; + transform: translateX(-50%); +} + +.dropdown-menu, +.dropdown-menu>li>a:not(.active) { color: var(--nav-default-color); background-color: var(--nav-default-background-color); } -.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus, -.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover { +.dropdown-menu>li>a.active { + color: var(--nav-hover-color); + background-color: var(--nav-hover-background-color); +} +.dropdown-menu>li>a:focus, +.dropdown-menu>li>a:hover { color: var(--nav-hover-color); background-color: var(--nav-hover-background-color); } diff --git a/flexmeasures/ui/templates/base.html b/flexmeasures/ui/templates/base.html index bdfc8c99b..ca83638d0 100644 --- a/flexmeasures/ui/templates/base.html +++ b/flexmeasures/ui/templates/base.html @@ -244,10 +244,42 @@ storeEndDate = new Date('{{ event_ends_before }}'); {% endif %} let replaySpeed = 100 + let chartType = 'bar_chart'; // initial chart type // todo: get from session? + + // Update chart type + document.addEventListener('DOMContentLoaded', function() { + var dropdownItems = document.querySelectorAll('.dropdown-item'); + for (var i = 0; i < dropdownItems.length; i++) { + + // Set initial chart type + if (dropdownItems[i].getAttribute('data-chart-type') === chartType) { + dropdownItems[i].classList.add('active'); + } + + // Add event listener + dropdownItems[i].addEventListener('click', function(e) { + e.preventDefault(); + chartType = this.getAttribute('data-chart-type'); + + // Update the active state of the dropdown items + var dropdownItems = document.querySelectorAll('.dropdown-item'); + dropdownItems.forEach(item => { + if (item === this) { + item.classList.add('active'); + } else { + item.classList.remove('active'); + } + }); + + // Reload daterange + picker.setDateRange(picker.getStartDate(), picker.getEndDate()); + }); + } + }); async function embedAndLoad(chartSpecsPath, elementId, datasetName, previousResult, startDate, endDate) { - await vegaEmbed('#'+elementId, chartSpecsPath + 'dataset_name=' + datasetName + '&width=container&include_sensor_annotations=false&include_asset_annotations=false&chart_type=matrix_chart', {{ chart_options | safe }}) + await vegaEmbed('#'+elementId, chartSpecsPath + 'dataset_name=' + datasetName + '&width=container&include_sensor_annotations=false&include_asset_annotations=false&chart_type=' + chartType, {{ chart_options | safe }}) .then(function (result) { // result.view is the Vega View, chartSpecsPath is the original Vega-Lite specification vegaView = result.view; diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index a031ca4ef..945f04c82 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -14,6 +14,15 @@
Select dates
+
From 0a859830c4a2c6d5978d488ea521b6db0a729e27 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Wed, 7 Jun 2023 13:36:08 +0200 Subject: [PATCH 04/34] Streamline button title with others Signed-off-by: F.N. Claessen --- flexmeasures/ui/templates/views/sensors.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/ui/templates/views/sensors.html b/flexmeasures/ui/templates/views/sensors.html index 945f04c82..ef8533ce6 100644 --- a/flexmeasures/ui/templates/views/sensors.html +++ b/flexmeasures/ui/templates/views/sensors.html @@ -16,7 +16,7 @@