Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 502 visually distinguish forecasts from measurements #503

Merged
Merged
1 change: 1 addition & 0 deletions documentation/changelog.rst
Expand Up @@ -10,6 +10,7 @@ New features

* Hit the replay button to replay what happened, available on the sensor and asset pages [see `PR #463 <http://www.github.com/FlexMeasures/flexmeasures/pull/463>`_]
* Visually distinguish forecasts/schedules (dashed lines) from measurements (solid lines), and expand the tooltip with timing info regarding the forecast/schedule horizon or measurement lag [see `PR #503 <http://www.github.com/FlexMeasures/flexmeasures/pull/503>`_]
* The asset page also allows to show sensor data from other assets that belong to the same account [see `PR #500 <http://www.github.com/FlexMeasures/flexmeasures/pull/500>`_]
* Improved import of time series data from CSV file: 1) drop duplicate records with warning, and 2) allow configuring which column contains explicit recording times for each data point (use case: import forecasts) [see `PR #501 <http://www.github.com/FlexMeasures/flexmeasures/pull/501>`_]

Bugfixes
Expand Down
16 changes: 14 additions & 2 deletions flexmeasures/api/dev/sensors.py
@@ -1,3 +1,5 @@
import datetime
import pytz
import json
import warnings

Expand Down Expand Up @@ -157,8 +159,18 @@ def get(self, id: int, asset: GenericAsset):

.. :quickref: Chart; Download asset attributes for use in charts
"""
attributes = ["name", "timezone", "timerange_of_sensors_to_show"]
return {attr: getattr(asset, attr) for attr in attributes}
# attributes = ["name", "timezone", "timerange_of_sensors_to_show"]
# return {attr: getattr(asset, attr) for attr in attributes}
attributes = ["name", "timezone"] # , "timerange_of_sensors_to_show"]
return {
**{attr: getattr(asset, attr) for attr in attributes},
**{
"timerange_of_sensors_to_show": {
"start": datetime.datetime(2020, 12, 3, 14, 0, tzinfo=pytz.utc),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a hard-coded value used in development?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. I encountered significant page loading delay here. I'll make a new ticket to make this asynchronous.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #515.

"end": datetime.datetime(2020, 12, 3, 14, 30, tzinfo=pytz.utc),
},
},
}


def get_sensor_or_abort(id: int) -> Sensor:
Expand Down
42 changes: 24 additions & 18 deletions flexmeasures/data/models/charts/belief_charts.py
Expand Up @@ -126,35 +126,41 @@ def chart_for_multiple_sensors(
"y": event_value_field_definition,
"color": FIELD_DEFINITIONS["source_name"],
"strokeDash": {
"condition": {
"test": "datum['belief_horizon'] > 0",
"value": [1, 2], # dashed
"field": "belief_horizon",
"type": "quantitative",
"bin": {
# Divide belief horizons into 2 bins by setting a very large bin size.
# The bins should be defined as follows: ex ante (>0) and ex post (<=0),
# but because the bin anchor is included in the ex-ante bin,
# and 0 belief horizons should be attributed to the ex-post bin,
# (and belief horizons are given with 1 ms precision,)
# the bin anchor is set at 1 ms before knowledge time to obtain: ex ante (>=1) and ex post (<1).
"anchor": 1,
"step": 8640000000000000, # JS max ms for a Date object (NB 10 times less than Python max ms)
# "step": timedelta.max.total_seconds() * 10**2,
},
"legend": {
# Belief horizons binned as 1 ms contain ex-ante beliefs; the other bin contains ex-post beliefs
"labelExpr": "datum.label > 0 ? 'ex ante' : 'ex post'",
"title": "Recorded",
},
"scale": {
# Positive belief horizons are clamped to 1, negative belief horizons are clamped to 0
"domain": [1, 0],
# belief horizons >= 1 ms get a dashed line, belief horizons < 1 ms get a solid line
"range": [[1, 2], [1, 0]],
},
"value": [1, 0], # solid
},
"detail": FIELD_DEFINITIONS["source"],
},
}
ex_ante_line_layer = {
**line_layer,
**{
"transform": [{"filter": "datum.belief_horizon > 0"}],
},
}
ex_post_line_layer = {
**line_layer,
**{
"transform": [{"filter": "datum.belief_horizon <= 0"}],
},
}
sensor_specs = {
"title": capitalize(sensor.name)
if sensor.name != sensor.sensor_type
else None,
"transform": [{"filter": f"datum.sensor.id == {sensor.id}"}],
"layer": [
ex_ante_line_layer,
ex_post_line_layer,
line_layer,
{
"mark": {
"type": "rect",
Expand Down
4 changes: 4 additions & 0 deletions flexmeasures/ui/static/js/flexmeasures.js
Expand Up @@ -325,6 +325,10 @@ function submit_sensor_type() {
* 'format': [<d3-format>, <breakpoint>],
* 'formatType': 'timedeltaFormat'
* }
* <d3-format> is a d3 format identifier, e.g. 'd' for decimal notation, rounded to integer.
* See https://github.com/d3/d3-format for more details.
* <breakpoint> is a scalar that decides the breakpoint from one duration unit to the next larger unit.
* For example, a breakpoint of 4 means we format 4 days as '4 days', but 3.96 days as '95 hours'.
*/
vega.expressionFunction('quantityWithUnitFormat', function(datum, params) {
return d3.format(params[0])(datum) + " " + params[1];
Expand Down