Skip to content

Commit

Permalink
Sensor chart ux improvements (#447)
Browse files Browse the repository at this point in the history
Introduce a collapsible sidepanel on the left of the sensor page, activated by hover or swipe, depending on the device. Also implements many smaller UX improvements:

- Show spinner while fetching new data
- Show single month and fewer custom ranges
- Rotate y-axis labels to improve legibility (addresses Issue #442)
- Update sensor data and annotations together instead of first come first serve
- Streamline calendar styling
- Streamline use of box shadows
- Fix positioning of chart actions button
- Move units to right side of tooltip
- Cancel previous request upon a new calendar selection
- Style navbar logo to have a consistent height and adjust the width of the navbar-header accordingly
- Actually load the font intended to be used in 0.10
- Make sure time axis labels aren't too close to each other


* Show updated sensor data and annotations together

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

* Show spinner while fetching new data

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

* Switch from id-based styling to class-based styling

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

* Move styling to css, and lower spinner

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

* Simplify and streamline datepicker fontsize

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

* Streamline datepicker margins

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

* Add margins and side panel activated on hover

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

* Correct margins and padding of side panel to allow for custom ranges at the bottom of the calendar

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

* Show single month

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

* Fewer custom ranges

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

* Side panel rounded similar to buttons rather than similar to cards

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

* Align box shadows of cards and calendar

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

* Non-transparent cards

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

* Simplified padding notation

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

* Move chart actions buttons away from the card's corner (negative margin)

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

* Rotate y-axis labels to improve legibility

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

* Remove sensor chart title if the same information is already contained in the y-axis label

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

* Move unit to right side of tooltip

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

* Style predefined datetime ranges

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

* Raise column to top without requiring flex display

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

* Rename sidepanel class and separate styling specific to the sidepanel being on the left

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

* Show spinner only while the promise is being fulfilled

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

* Cancel previous request when the user makes a new request

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

* Do not let spinner block the full page height, so the sensor table navigation can still be used

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

* Change header and label colors inside the sidepanel to contrast against the sidepanel background

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

* Style navbar logo to have a consistent height and adjust the width of the navbar-header accordingly

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

* Actually load intended font

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

* Enforce separation of time axis labels

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

* Add return type annotation and docs: applying chart defaults returns a dictionary with vega-lite specs

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

* Resolve hover glitch when exiting either the list of months or the list of years with the pointer. This stops the side panel from collapsing and reopening.

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

* Enable swiping for left sidepanel

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

* Stop using redundant litepicker plugin, which was messing with calendar styling

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

* Fix test

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 Jun 25, 2022
1 parent a436797 commit 43aa25d
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 87 deletions.
1 change: 1 addition & 0 deletions documentation/changelog.rst
Expand Up @@ -8,6 +8,7 @@ v0.11.0 | June XX, 2022
New features
-------------
* Individual sensor charts show available annotations [see `PR #428 <http://www.github.com/FlexMeasures/flexmeasures/pull/428>`_]
* Collapsible sidepanel (hover/swipe) used for date selection on sensor charts, and various styling improvements [see `PR #447 <http://www.github.com/FlexMeasures/flexmeasures/pull/447>`_]

Bugfixes
-----------
Expand Down
10 changes: 7 additions & 3 deletions flexmeasures/data/models/charts/belief_charts.py
Expand Up @@ -9,13 +9,14 @@ def bar_chart(
unit = sensor.unit if sensor.unit else "a.u."
event_value_field_definition = dict(
title=f"{capitalize(sensor.sensor_type)} ({unit})",
format=".3s",
format=[".3s", unit],
formatType="quantityWithUnitFormat",
stack=None,
**FIELD_DEFINITIONS["event_value"],
)
chart_specs = {
"description": "A simple bar chart.",
"title": capitalize(sensor.name),
"title": capitalize(sensor.name) if sensor.name != sensor.sensor_type else None,
"mark": "bar",
"encoding": {
"x": FIELD_DEFINITIONS["event_start"],
Expand All @@ -25,7 +26,10 @@ def bar_chart(
"opacity": {"value": 0.7},
"tooltip": [
FIELD_DEFINITIONS["full_date"],
event_value_field_definition,
{
**event_value_field_definition,
**dict(title=f"{capitalize(sensor.sensor_type)}"),
},
FIELD_DEFINITIONS["source"],
],
},
Expand Down
6 changes: 5 additions & 1 deletion flexmeasures/data/models/charts/defaults.py
Expand Up @@ -17,11 +17,13 @@
field="event_start",
type="temporal",
title=None,
axis={"labelOverlap": True, "labelSeparation": 1},
),
"event_end": dict(
field="event_end",
type="temporal",
title=None,
axis={"labelOverlap": True, "labelSeparation": 1},
),
"event_value": dict(
field="event_value",
Expand Down Expand Up @@ -105,6 +107,7 @@
titleFontSize=FONT_SIZE,
labelFontSize=FONT_SIZE,
),
axisY={"titleAngle": 0, "titleAlign": "left", "titleY": -15, "titleX": -40},
title=dict(
fontSize=FONT_SIZE,
),
Expand All @@ -128,7 +131,8 @@

def apply_chart_defaults(fn):
@wraps(fn)
def decorated_chart_specs(*args, **kwargs):
def decorated_chart_specs(*args, **kwargs) -> dict:
""":returns: dict with vega-lite specs, even when applied to an Altair chart."""
dataset_name = kwargs.pop("dataset_name", None)
include_annotations = kwargs.pop("include_annotations", None)
if isinstance(fn, Callable):
Expand Down
2 changes: 1 addition & 1 deletion flexmeasures/data/models/charts/test_chart_defaults.py
Expand Up @@ -6,4 +6,4 @@
def test_default_encodings():
"""Check default encodings for valid vega-lite specifications."""
for field_name, field_definition in FIELD_DEFINITIONS.items():
assert alt.StringFieldDefWithCondition(**field_definition)
assert alt.PositionFieldDef(**field_definition)
4 changes: 1 addition & 3 deletions flexmeasures/data/models/time_series.py
Expand Up @@ -366,9 +366,7 @@ def chart(
# Set up chart specification
if dataset_name is None:
dataset_name = "sensor_" + str(self.id)
self.sensor_type = (
self.name
) # todo remove this placeholder when sensor types are modelled
self.sensor_type = self.get_attribute("sensor_type", self.name)
chart_specs = chart_type_to_chart_specs(
chart_type,
sensor=self,
Expand Down
147 changes: 104 additions & 43 deletions flexmeasures/ui/static/css/flexmeasures.css
Expand Up @@ -33,7 +33,30 @@
--delete-color: var(--red);
}

@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJbecmNE.woff2) format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJnecmNE.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJfecg.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

body {
padding-top: 100px; /* This default is overridden by flexmeasures.js to support a fluid navbar */
Expand All @@ -43,6 +66,12 @@ body *:not(.fa, .glyphicon) {
font-family: 'Poppins', sans-serif;
}

@media (min-width: 768px) {
.on-top-md {
z-index: 1010;
}
}

h1 {
font-size: 35px;
color: var(--primary-color);
Expand All @@ -63,6 +92,12 @@ h1, h2, h3 {
font-weight: 600;
color: var(--primary-color);
}
.sidepanel h1,
.sidepanel h2,
.sidepanel h3,
.sidepanel label {
color: var(--white) !important;
}

hr {
border-top: none;
Expand Down Expand Up @@ -108,8 +143,14 @@ form button[type="submit"] {
}

#spinner {
padding-top: 50px !important;
padding-bottom: 50px !important;
padding-top: 200px;
text-align: center;
position: absolute;
z-index: 10;
font-size: 8px;
top: 0;
left: 0;
width: 100%;
}

.legend {
Expand Down Expand Up @@ -170,7 +211,6 @@ p.error {
/* --- Nav Bar --- */

.navbar-tool-name {
margin-right: 30px;
color: var(--secondary-color);
}

Expand Down Expand Up @@ -220,8 +260,8 @@ p.error {

.navbar-brand {
display: inline-block;
width: 200px;
height: 100%;
padding: 10px 15px;
}

.navbar-brand a {
Expand All @@ -233,10 +273,6 @@ p.error {
color: var(--secondary-color) !important;
}

.navbar-brand a span img {
width: 200px;
}

.navbar-default .navbar-collapse, .navbar-default .navbar-form {
border-color: var(--primary-border-color);
}
Expand Down Expand Up @@ -264,8 +300,8 @@ p.error {

@media (min-width: 768px) {
.navbar-nav>li>a {
padding-top: 28px;
padding-bottom: 28px;
padding-top: 20px;
padding-bottom: 20px;
}

.navbar-default .dropdown-menu a {
Expand Down Expand Up @@ -301,8 +337,7 @@ p.error {
}

#navbar-logo {
height: 50px;
position: fixed; top: 0; left: 0;
height: 40px;
}

/* --- End Nav Bar --- */
Expand Down Expand Up @@ -877,13 +912,48 @@ body .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
/* ---- End Modal ---- */


/* ---- Date picker ---- */
/* ---- Side panel ---- */

#datepicker {
margin-top: 50px;
margin-bottom: 30px;
@media (min-width: 768px) {
.sidepanel-container {
z-index: 20;
}
.sidepanel {
background: var(--nav-default-background-color);
width: calc(var(--litepicker-day-width) * 9);
margin: 15px;
padding: 20px 15px;
transition: .3s;
-webkit-transition: .3s;
-moz-transition: .3s;
-ms-transition: .3s;
-o-transition: .3s;
}
.left-sidepanel {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.sidepanel-container > .left-sidepanel:not(.sidepanel-show) {
transform: translateX(-106%);
}
@media (hover: hover) {
.sidepanel-container:hover > .left-sidepanel,
.sidepanel-container:focus-within > .left-sidepanel {
transform: translateX(-30px);
}
}
@media (hover: none) {
.sidepanel-container > .left-sidepanel.sidepanel-show {
transform: translateX(-30px);
}
}
}


/* ---- Date picker ---- */

.datetimepicker input {
width: 100%;
}
Expand All @@ -897,7 +967,6 @@ body .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
outline: none;
border-radius: 4px;
border: 1px solid #ddd;
font-size: 13px;
color: var(--nav-default-color);
background: var(--nav-default-background-color);
-webkit-border-radius: 4px;
Expand All @@ -907,8 +976,9 @@ body .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
}

.container__predefined-ranges {
padding: 20px;
margin-right: 10px;
padding: 0px;
margin-top: 15px;
justify-content: space-around;
box-shadow: 0 0 10px rgba(0,0,0,.1) !important;
border-radius: 6px !important;
-webkit-border-radius: 6px !important;
Expand All @@ -918,12 +988,17 @@ body .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
}

.container__predefined-ranges button {
font-size: 14px;
font-weight: 500;
cursor: pointer !important;
border-right: 1px solid var(--primary-border-color) !important;
flex: 1 1 auto;
}
.container__predefined-ranges > button:last-child {
border-right: none !important;
}

.container__months {
box-shadow: 0 0 10px rgba(0,0,0,.1) !important;
border-radius: 6px !important;
-webkit-border-radius: 6px !important;
-moz-border-radius: 6px !important;
Expand All @@ -932,22 +1007,13 @@ body .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
justify-content: center;
}

.month-item-weekdays-row {
font-size: 14px;
}

.week-number {
font-size: 14px !important;
}

.litepicker .container__days .day-item.is-today {
color: var(--nav-current-color);
background: var(--secondary-transparent);
}

.litepicker .container__days .day-item {
color: var(--litepicker-day-color);
font-size: 14px;
cursor: pointer;
width: 38px;
height: 38px;
Expand Down Expand Up @@ -1058,11 +1124,9 @@ body .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
fill: var(--secondary-color) !important;
}

@media (max-width:525px) {
@media (max-width: 525px) {
#datepicker .container__main {
flex-wrap: wrap;
margin-left: 15px;
margin-right: 15px;
}

#datepicker .container__predefined-ranges {
Expand All @@ -1077,19 +1141,20 @@ body .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {

/* --- Sensor Data --- */

.sensor-data .sensor-chart-main {
padding: 0;
}

#sensorchart {
padding: 20px 15px 20px 15px;
.card {
background: var(--white);
margin: 15px;
padding: 20px 15px;
box-shadow: 0 0 10px rgba(0,0,0,.1);
border-radius: 6px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
-ms-border-radius: 6px;
-o-border-radius: 6px;
}
.card.vega-embed summary {
transform: translate(-15px, 15px);
}

.role-title-text text {
font-size: 20px;
Expand All @@ -1100,10 +1165,6 @@ body .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
margin: 0 !important;
}

#sensorchart {
margin-bottom: 40px;
}

#vg-tooltip-element {
font-size: 16px;
}
Expand Down
11 changes: 11 additions & 0 deletions flexmeasures/ui/static/js/flexmeasures.js
Expand Up @@ -310,3 +310,14 @@ function submit_market() {
function submit_sensor_type() {
$("#sensor_type-form").attr("action", empty_location).submit();
}

/** Tooltips: Register custom formatter for quantities incl. units
Usage:
{
'format': [<d3-format>, <sensor unit>],
'formatType': 'quantityWithUnitFormat'
}
*/
vega.expressionFunction('quantityWithUnitFormat', function(datum, params) {
return d3.format(params[0])(datum) + " " + params[1];
});

0 comments on commit 43aa25d

Please sign in to comment.