Skip to content

Commit

Permalink
Add area chart, stacked chart and manual configurability of chart sty…
Browse files Browse the repository at this point in the history
…le for ChartWidget
  • Loading branch information
mhthies committed Apr 1, 2024
1 parent 20ebc00 commit 0a236be
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 9 deletions.
7 changes: 7 additions & 0 deletions docs/web/log_widgets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ Web UI Widgets for showing Logs
:members:
:member-order:

.. autoclass:: ChartPlotStyle
:members:
:member-order:

.. autoclass:: ChartLineInterpolation
:members:
:member-order:
10 changes: 9 additions & 1 deletion example/ui_logging_showcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from shc.interfaces.in_memory_data_logging import InMemoryDataLogVariable
import shc.web.log_widgets
from shc.data_logging import AggregationMethod
from shc.web.log_widgets import ChartDataSpec, LogListDataSpec
from shc.web.log_widgets import ChartDataSpec, LogListDataSpec, ChartPlotStyle, ChartLineInterpolation
from shc.web.widgets import icon

random_float_log = InMemoryDataLogVariable(float, keep=datetime.timedelta(minutes=10))
Expand Down Expand Up @@ -74,6 +74,14 @@ async def new_bool(_v, _o):
ChartDataSpec(random_float_log, "random float", scale_factor=10)
]))

index_page.add_item(shc.web.log_widgets.ChartWidget(datetime.timedelta(minutes=5), [
ChartDataSpec(random_float_log, "random float", scale_factor=10, stack_group="a",
line_interpolation=ChartLineInterpolation.STEP_BEFORE, plot_style=ChartPlotStyle.AREA),
ChartDataSpec(random_float_log, "random float", scale_factor=10, stack_group="a",
line_interpolation=ChartLineInterpolation.STEP_BEFORE, plot_style=ChartPlotStyle.AREA),
ChartDataSpec(random_float_log, "random float", scale_factor=15, plot_style=ChartPlotStyle.LINE),
]))

index_page.add_item(shc.web.log_widgets.ChartWidget(datetime.timedelta(minutes=5), [
ChartDataSpec(random_float_log, "avg", aggregation=AggregationMethod.AVERAGE, unit_symbol="°C"),
ChartDataSpec(random_float_log, "min", aggregation=AggregationMethod.MINIMUM, unit_symbol="°C"),
Expand Down
47 changes: 44 additions & 3 deletions shc/web/log_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"""
from dataclasses import dataclass
import datetime
import enum
from typing import Iterable, Optional, Generic, Union, Callable, Tuple, List

from markupsafe import Markup
Expand Down Expand Up @@ -118,6 +119,32 @@ def get_connectors(self) -> Iterable[WebUIConnector]:
return self.connectors


class ChartPlotStyle(enum.Enum):
#: Uses `LINE_DOTS` for aggregated values, otherwise `LINE_FILLED`
AUTO = "auto"
#: A simple line plot, no dots, no background filling
LINE = "line"
#: A line plot with dots, no background filling
LINE_DOTS = "line_dots"
#: A pretty line plot with slightly colored background area
LINE_FILLED = "line_filled"
#: An area plot without visible line but stronger colored filled area
AREA = "area"


class ChartLineInterpolation(enum.Enum):
#: Uses `SMOOTH` for aggregated values, otherwise `LINEAR`
AUTO = "auto"
#: Use bezier line interpolation
SMOOTH = "smooth"
#: Use linear interpolation (straight lines between data points)
LINEAR = "linear"
#: Use stepped graph, beginning a horizontal line at each data point
STEP_BEFORE = "before"
#: Use stepped graph with horizontal lines ending at each data point
STEP_AFTER = "after"


@dataclass
class ChartDataSpec:
"""Specification of one data log source and the formatting of its datapoints within a :class:`ChartWidget`"""
Expand All @@ -134,6 +161,12 @@ class ChartDataSpec:
scale_factor: float = 1.0
#: Unit symbol to be shown after the value in the Chart tooltip
unit_symbol: str = ""
#: If not None, this data row will be stacked upon (values added to) previous data rows with the same `stack_group`
stack_group: Optional[str] = None
#: Select different plot styles (area vs. line plot, dots on/off)
plot_style: ChartPlotStyle = ChartPlotStyle.AUTO
#: Select different line interpolation styles (smoothed, linear, stepped)
line_interpolation: ChartLineInterpolation = ChartLineInterpolation.AUTO


class ChartWidget(WebPageItem):
Expand Down Expand Up @@ -213,14 +246,22 @@ def __init__(self, interval: datetime.timedelta, data_spec: List[ChartDataSpec]
aggregation_interval, align_to=self.align_ticks_to,
converter=None if spec.scale_factor == 1.0 else lambda x: x*spec.scale_factor,
include_previous=True)
line_interpolation = spec.line_interpolation
if line_interpolation == ChartLineInterpolation.AUTO:
line_interpolation = ChartLineInterpolation.SMOOTH if is_aggregated else ChartLineInterpolation.LINEAR
plot_style = spec.plot_style
if plot_style == ChartPlotStyle.AUTO:
plot_style = ChartPlotStyle.LINE_DOTS if is_aggregated else ChartPlotStyle.LINE_FILLED
self.connectors.append(connector)
self.row_specs.append({'id': id(connector),
'stepped_graph': is_aggregated,
'show_points': is_aggregated,
'color': spec.color if spec.color is not None else self.COLORS[i % len(self.COLORS)],
'label': spec.label,
'unit_symbol': spec.unit_symbol,
'extend_graph_to_now': not is_aggregated})
'extend_graph_to_now': not is_aggregated,
'stack_group': spec.stack_group,
'style': plot_style.value,
'interpolation': line_interpolation.value,
})

async def render(self) -> str:
return await jinja_env.get_template('log/chart.htm').render_async(
Expand Down
24 changes: 19 additions & 5 deletions web_ui_src/widgets/log_widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,33 @@ function LineChartWidget(domElement, _writeValue) {
// datasets (and subscribeIds)
let datasets = [];
let dataMap = new Map(); // maps the Python datapoint/object id to the associated chart dataset's data list
let numStacked = 0;
for (const spec of seriesSpec) {
this.subscribeIds.push(spec.id);
let data = [];
datasets.push({
data: data,
label: spec.label,
backgroundColor: `rgba(${spec.color[0]}, ${spec.color[1]}, ${spec.color[2]}, 0.1)`,
backgroundColor: (spec.style == "area"
? `rgba(${spec.color[0]}, ${spec.color[1]}, ${spec.color[2]}, 0.5)`
: `rgba(${spec.color[0]}, ${spec.color[1]}, ${spec.color[2]}, 0.1)`),
borderColor: `rgba(${spec.color[0]}, ${spec.color[1]}, ${spec.color[2]}, .5)`,
pointBackgroundColor: `rgba(${spec.color[0]}, ${spec.color[1]}, ${spec.color[2]}, 0.5)`,
pointBorderColor: `rgba(${spec.color[0]}, ${spec.color[1]}, ${spec.color[2]}, 0.5)`,
stepped: spec.stepped_graph ? undefined : 'before',
pointRadius: spec.show_points ? 3 : 0,
pointHitRadius: 3,
tension: 0.2,
stepped: ["before", "after"].includes(spec.interpolation) ? spec.interpolation : undefined,
pointRadius: spec.style == "line_dots" ? 3 : 0,
pointHitRadius: 5,
tension: spec.interpolation == "smooth" ? 0.2 : 0.0,
stack: spec.stack_group,
fill: (spec.style == "area" || spec.style == "line_filled"
? (spec.stack_group !== null && numStacked > 0 ? "-1" : "origin")
: undefined),
showLine: ["line", "line_filled", "line_dots"].includes(spec.style),
});
dataMap.set(spec.id, [data, spec]);
if (spec.stack_group !== null) {
numStacked++;
}
}

// Initialize chart
Expand Down Expand Up @@ -206,6 +217,9 @@ function LineChartWidget(domElement, _writeValue) {
ticks: {
source: 'labels',
}
},
y: {
stacked: numStacked > 0 ? true : false,
}
},
responsive: true
Expand Down

0 comments on commit 0a236be

Please sign in to comment.