Skip to content

Commit

Permalink
Mobile friendly (responsive) charts of sensor data, and such charts c…
Browse files Browse the repository at this point in the history
…an be requested with a custom width and height.

Responsive sensor chart (#313)

* Append to transforms possibly already coming out of the chart specs

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Let chart specs derive title, quantity and unit from Sensor object

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Derive bar width from sensor's event resolution

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Fix attribute call

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Responsive sensor charts: scales width to container, which is better for mobile. Also exposes width and height as overridable chart properties for getting charts by API.

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Changelog entry

Signed-off-by: F.N. Claessen <felix@seita.nl>
  • Loading branch information
Flix6x committed Jan 12, 2022
1 parent f7c6ab0 commit 932034f
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 16 deletions.
1 change: 1 addition & 0 deletions documentation/changelog.rst
Expand Up @@ -11,6 +11,7 @@ v0.8.0 | November XX, 2021
New features
-----------
* Charts with sensor data can be requested in one of the supported [`vega-lite themes <https://github.com/vega/vega-themes#included-themes>`_] (incl. a dark theme) [see `PR #221 <http://www.github.com/FlexMeasures/flexmeasures/pull/221>`_]
* Mobile friendly (responsive) charts of sensor data, and such charts can be requested with a custom width and height [see `PR #313 <http://www.github.com/FlexMeasures/flexmeasures/pull/313>`_]
* Schedulers take into account round-trip efficiency if set [see `PR #291 <http://www.github.com/FlexMeasures/flexmeasures/pull/291>`_]

Bugfixes
Expand Down
2 changes: 2 additions & 0 deletions flexmeasures/api/dev/sensors.py
Expand Up @@ -29,6 +29,8 @@ class SensorAPI(FlaskView):
"beliefs_before": AwareDateTimeField(format="iso", required=False),
"include_data": fields.Boolean(required=False),
"dataset_name": fields.Str(required=False),
"height": fields.Str(required=False),
"width": fields.Str(required=False),
},
location="query",
)
Expand Down
25 changes: 19 additions & 6 deletions flexmeasures/data/models/charts/belief_charts.py
@@ -1,21 +1,25 @@
from flexmeasures.data.models.charts.defaults import FIELD_DEFINITIONS
from flexmeasures.utils.flexmeasures_inflection import capitalize


def bar_chart(title: str, quantity: str = "unknown quantity", unit: str = "a.u."):
if not unit:
unit = "a.u."
def bar_chart(
sensor: "Sensor", # noqa F821
**override_chart_specs: dict,
):
unit = sensor.unit if sensor.unit else "a.u."
event_value_field_definition = dict(
title=f"{quantity} ({unit})",
title=f"{capitalize(sensor.sensor_type)} ({unit})",
format=".3s",
stack=None,
**FIELD_DEFINITIONS["event_value"],
)
return {
chart_specs = {
"description": "A simple bar chart.",
"title": title,
"title": capitalize(sensor.name),
"mark": "bar",
"encoding": {
"x": FIELD_DEFINITIONS["event_start"],
"x2": FIELD_DEFINITIONS["event_end"],
"y": event_value_field_definition,
"color": FIELD_DEFINITIONS["source"],
"opacity": {"value": 0.7},
Expand All @@ -25,4 +29,13 @@ def bar_chart(title: str, quantity: str = "unknown quantity", unit: str = "a.u."
FIELD_DEFINITIONS["source"],
],
},
"transform": [
{
"calculate": f"datum.event_start + {sensor.event_resolution.total_seconds() * 1000}",
"as": "event_end",
},
],
}
for k, v in override_chart_specs.items():
chart_specs[k] = v
return chart_specs
21 changes: 17 additions & 4 deletions flexmeasures/data/models/charts/defaults.py
Expand Up @@ -16,6 +16,11 @@
type="temporal",
title=None,
),
"event_end": dict(
field="event_end",
type="temporal",
title=None,
),
"event_value": dict(
field="event_value",
type="quantitative",
Expand Down Expand Up @@ -48,14 +53,22 @@ def decorated_chart_specs(*args, **kwargs):
chart_specs.pop("$schema")
if dataset_name:
chart_specs["data"] = {"name": dataset_name}
chart_specs["height"] = HEIGHT
chart_specs["width"] = WIDTH
chart_specs["transform"] = [

# Fall back to default height and width, if needed
if "height" not in chart_specs:
chart_specs["height"] = HEIGHT
if "width" not in chart_specs:
chart_specs["width"] = WIDTH

# Add transform function to calculate full date
if "transform" not in chart_specs:
chart_specs["transform"] = []
chart_specs["transform"].append(
{
"as": "full_date",
"calculate": f"timeFormat(datum.event_start, '{TIME_FORMAT}')",
}
]
)
return chart_specs

return decorated_chart_specs
5 changes: 1 addition & 4 deletions flexmeasures/data/models/time_series.py
Expand Up @@ -28,7 +28,6 @@
from flexmeasures.data.models.generic_assets import GenericAsset
from flexmeasures.data.models.validation_utils import check_required_attributes
from flexmeasures.utils.time_utils import server_now
from flexmeasures.utils.flexmeasures_inflection import capitalize


class Sensor(db.Model, tb.SensorDBMixin, AuthModelMixin):
Expand Down Expand Up @@ -270,9 +269,7 @@ def chart(
) # todo remove this placeholder when sensor types are modelled
chart_specs = chart_type_to_chart_specs(
chart_type,
title=capitalize(self.name),
quantity=capitalize(self.sensor_type),
unit=self.unit,
sensor=self,
dataset_name=dataset_name,
**kwargs,
)
Expand Down
9 changes: 7 additions & 2 deletions flexmeasures/ui/templates/views/sensors.html
Expand Up @@ -9,7 +9,12 @@
<div class="charts text-center">
<div class="row"><div class="alert alert-info" id="tzwarn" style="display:none;"></div></div>
<div class="row"><div id="datepicker"></div></div>
<div class="row"><div id="sensorchart"></div></div><hr>
<div class="row">
<div class="col-sm-12">
<div id="sensorchart" style="width: 100%;"></div>
</div>
</div>
<hr>
</div>

<script src="https://d3js.org/d3.v6.min.js"></script>
Expand All @@ -31,7 +36,7 @@

async function embedAndLoad(chartSpecsPath, elementId, datasetName) {

await vegaEmbed('#'+elementId, chartSpecsPath + '?dataset_name=' + datasetName, {{ chart_options | safe }})
await vegaEmbed('#'+elementId, chartSpecsPath + '?dataset_name=' + datasetName + '&width=container', {{ chart_options | safe }})
.then(function (result) {
// result.view is the Vega View, chartSpecsPath is the original Vega-Lite specification
vegaView = result.view;
Expand Down

0 comments on commit 932034f

Please sign in to comment.