From fcd4ac7292d74eb75fe8fcc494b0cbc6684bb563 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Sun, 28 Oct 2018 20:01:01 +0200 Subject: [PATCH] WIP #120 Numeric state charts --- lib/entity_class/entity.class.dart | 21 ++- lib/entity_class/other_entity.class.dart | 14 ++ ...n_entity.class.dart => slider_entity.dart} | 11 +- lib/entity_collection.class.dart | 3 + .../history_chart/entity_history.dart | 30 +++- .../numeric_state_history_chart.dart | 140 ++++++++++++++++++ .../simple_state_history_chart.dart | 28 ++-- lib/main.dart | 4 +- 8 files changed, 221 insertions(+), 30 deletions(-) create mode 100644 lib/entity_class/other_entity.class.dart rename lib/entity_class/{sun_entity.class.dart => slider_entity.dart} (68%) create mode 100644 lib/entity_widgets/history_chart/numeric_state_history_chart.dart diff --git a/lib/entity_class/entity.class.dart b/lib/entity_class/entity.class.dart index dd5d198..a01c49d 100644 --- a/lib/entity_class/entity.class.dart +++ b/lib/entity_class/entity.class.dart @@ -5,15 +5,19 @@ class EntityColors { "on": Colors.amber, "auto": Colors.amber, "idle": Colors.amber, - "off": Color.fromRGBO(68, 115, 158, 1.0), - "default": Color.fromRGBO(68, 115, 158, 1.0), - "heat": Colors.redAccent, - "cool": Colors.lightBlue, - "unavailable": Colors.black26, - "unknown": Colors.black26, "playing": Colors.amber, "above_horizon": Colors.amber, "home": Colors.amber, + "open": Colors.amber, + "off": Color.fromRGBO(68, 115, 158, 1.0), + "closed": Color.fromRGBO(68, 115, 158, 1.0), + "default": Color.fromRGBO(68, 115, 158, 1.0), + "heat": Colors.redAccent, + "cool": Colors.lightBlue, + "closing": Colors.cyan, + "opening": Colors.purple, + "unavailable": Colors.black26, + "unknown": Colors.black26, }; static Color stateColor(String state) { @@ -69,8 +73,8 @@ class Entity { DateTime _lastUpdated; List childEntities = []; - List attributesToShow = ["all"]; + int historyWidgetType = EntityHistoryWidgetType.simple; String get displayName => attributes["friendly_name"] ?? (attributes["name"] ?? "_"); @@ -88,6 +92,7 @@ class Entity { List get childEntityIds => attributes["entity_id"] ?? []; String get lastUpdated => _getLastUpdatedFormatted(); bool get isHidden => attributes["hidden"] ?? false; + double get doubleState => double.tryParse(state) ?? 0.0; Entity(Map rawData) { update(rawData); @@ -167,7 +172,7 @@ class Entity { Widget buildHistoryWidget() { return EntityHistoryWidget( - type: EntityHistoryWidgetType.simplest, + type: historyWidgetType, ); } diff --git a/lib/entity_class/other_entity.class.dart b/lib/entity_class/other_entity.class.dart new file mode 100644 index 0000000..4dadd4b --- /dev/null +++ b/lib/entity_class/other_entity.class.dart @@ -0,0 +1,14 @@ +part of '../main.dart'; + +class SunEntity extends Entity { + SunEntity(Map rawData) : super(rawData); +} + +class SensorEntity extends Entity { + + @override + int historyWidgetType = EntityHistoryWidgetType.valueToTime; + + SensorEntity(Map rawData) : super(rawData); + +} \ No newline at end of file diff --git a/lib/entity_class/sun_entity.class.dart b/lib/entity_class/slider_entity.dart similarity index 68% rename from lib/entity_class/sun_entity.class.dart rename to lib/entity_class/slider_entity.dart index b83768b..b34abe3 100644 --- a/lib/entity_class/sun_entity.class.dart +++ b/lib/entity_class/slider_entity.dart @@ -1,16 +1,11 @@ part of '../main.dart'; -class SunEntity extends Entity { - SunEntity(Map rawData) : super(rawData); -} - class SliderEntity extends Entity { SliderEntity(Map rawData) : super(rawData); - double get minValue => attributes["min"] ?? 0.0; - double get maxValue => attributes["max"] ?? 100.0; - double get valueStep => attributes["step"] ?? 1.0; - double get doubleState => double.tryParse(state) ?? 0.0; + double get minValue => _getDoubleAttributeValue("min") ?? 0.0; + double get maxValue =>_getDoubleAttributeValue("max") ?? 100.0; + double get valueStep => _getDoubleAttributeValue("step") ?? 1.0; @override Widget _buildStatePart(BuildContext context) { diff --git a/lib/entity_collection.class.dart b/lib/entity_collection.class.dart index b67af3b..cf3df3e 100644 --- a/lib/entity_collection.class.dart +++ b/lib/entity_collection.class.dart @@ -38,6 +38,9 @@ class EntityCollection { case 'sun': { return SunEntity(rawEntityData); } + case 'sensor': { + return SensorEntity(rawEntityData); + } case "automation": case "input_boolean": case "switch": { diff --git a/lib/entity_widgets/history_chart/entity_history.dart b/lib/entity_widgets/history_chart/entity_history.dart index 34a48bd..102ccf7 100644 --- a/lib/entity_widgets/history_chart/entity_history.dart +++ b/lib/entity_widgets/history_chart/entity_history.dart @@ -1,8 +1,9 @@ part of '../../main.dart'; class EntityHistoryWidgetType { - static const int simplest = 0; + static const int simple = 0; static const int valueToTime = 1; + static const int randomColors = 2; } class EntityHistoryWidget extends StatefulWidget { @@ -69,9 +70,7 @@ class _EntityHistoryWidgetState extends State { ); } else { children.add( - SimpleStateHistoryChartWidget( - rawHistory: _history - ) + _selectChartWidget() ); } children.add(Divider()); @@ -83,4 +82,27 @@ class _EntityHistoryWidgetState extends State { ); } + Widget _selectChartWidget() { + switch (widget.type) { + case EntityHistoryWidgetType.simple: { + return SimpleStateHistoryChartWidget( + rawHistory: _history, + ); + } + + case EntityHistoryWidgetType.valueToTime: { + return NumericStateHistoryChartWidget( + rawHistory: _history, + ); + } + + default: { + return SimpleStateHistoryChartWidget( + rawHistory: _history, + ); + } + } + + } + } \ No newline at end of file diff --git a/lib/entity_widgets/history_chart/numeric_state_history_chart.dart b/lib/entity_widgets/history_chart/numeric_state_history_chart.dart new file mode 100644 index 0000000..ef30478 --- /dev/null +++ b/lib/entity_widgets/history_chart/numeric_state_history_chart.dart @@ -0,0 +1,140 @@ +part of '../../main.dart'; + +class NumericStateHistoryChartWidget extends StatefulWidget { + final rawHistory; + + const NumericStateHistoryChartWidget({Key key, this.rawHistory}) : super(key: key); + + + @override + State createState() { + return new _NumericStateHistoryChartWidgetState(); + } + +} + +class _NumericStateHistoryChartWidgetState extends State { + + int _selectedId = -1; + List> _parsedHistory; + + @override + Widget build(BuildContext context) { + _parsedHistory = _parseHistory(); + DateTime selectedTime; + double selectedState; + if ((_selectedId > -1) && (_parsedHistory != null) && (_parsedHistory.first.data.length >= (_selectedId + 1))) { + selectedTime = _parsedHistory.first.data[_selectedId].time; + selectedState = _parsedHistory.first.data[_selectedId].value; + } + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + HistoryControlWidget( + selectedTimeStart: selectedTime, + selectedState: "$selectedState", + onPrevTap: () => _selectPrev(), + onNextTap: () => _selectNext(), + ), + SizedBox( + height: 150.0, + child: charts.TimeSeriesChart( + _parsedHistory, + animate: false, + primaryMeasureAxis: new charts.NumericAxisSpec( + tickProviderSpec: + new charts.BasicNumericTickProviderSpec(zeroBound: false)), + dateTimeFactory: const charts.LocalDateTimeFactory(), + defaultRenderer: charts.LineRendererConfig(), + customSeriesRenderers: [ + new charts.PointRendererConfig( + // ID used to link series to this renderer. + customRendererId: 'valuePoints') + ], + /*primaryMeasureAxis: charts.NumericAxisSpec( + renderSpec: charts.NoneRenderSpec() + ),*/ + selectionModels: [ + new charts.SelectionModelConfig( + type: charts.SelectionModelType.info, + listener: (model) => _onSelectionChanged(model), + ) + ], + behaviors: [ + charts.PanAndZoomBehavior(), + ], + ), + ) + ], + ); + } + + List> _parseHistory() { + List data = []; + DateTime now = DateTime.now(); + for (var i = 0; i < widget.rawHistory.length; i++) { + var stateData = widget.rawHistory[i]; + DateTime time = DateTime.tryParse(stateData["last_updated"])?.toLocal(); + data.add(NumericEntityStateHistoryMoment(double.tryParse(stateData["state"]), time, i)); + } + data.add(NumericEntityStateHistoryMoment(data.last.value, now, widget.rawHistory.length)); + return [ + new charts.Series( + id: 'State', + colorFn: (NumericEntityStateHistoryMoment historyMoment, __) => EntityColors.historyStateColor("unavailable"), + domainFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.time, + measureFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.value, + data: data, + ), + new charts.Series( + id: 'State', + radiusPxFn: (NumericEntityStateHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 4.0 : 2.0, + colorFn: (NumericEntityStateHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? EntityColors.historyStateColor("on") : EntityColors.historyStateColor("off"), + domainFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.time, + measureFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.value, + data: data, + )..setAttribute(charts.rendererIdKey, 'valuePoints') + ]; + } + + void _selectPrev() { + if (_selectedId > 0) { + setState(() { + _selectedId -= 1; + }); + } + } + + void _selectNext() { + if (_selectedId < (_parsedHistory.first.data.length - 2)) { + setState(() { + _selectedId += 1; + }); + } + } + + void _onSelectionChanged(charts.SelectionModel model) { + final selectedDatum = model.selectedDatum; + + int selectedId; + + if (selectedDatum.isNotEmpty) { + selectedId = selectedDatum.first.datum.id; + setState(() { + _selectedId = selectedId; + }); + } else { + setState(() { + }); + } + } +} + +class NumericEntityStateHistoryMoment { + final DateTime time; + final double value; + final int id; + + NumericEntityStateHistoryMoment(this.value, this.time, this.id); +} \ No newline at end of file diff --git a/lib/entity_widgets/history_chart/simple_state_history_chart.dart b/lib/entity_widgets/history_chart/simple_state_history_chart.dart index 7300f03..2ea8c6c 100644 --- a/lib/entity_widgets/history_chart/simple_state_history_chart.dart +++ b/lib/entity_widgets/history_chart/simple_state_history_chart.dart @@ -162,15 +162,7 @@ class HistoryControlWidget extends StatelessWidget { ), ), ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("${formatDate(selectedTimeStart, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}", textAlign: TextAlign.left,), - Text("${formatDate(selectedTimeEnd ?? selectedTimeStart, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}", textAlign: TextAlign.left,), - ], - ), - ), + _buildTime(), IconButton( icon: Icon(Icons.chevron_right), padding: EdgeInsets.all(0.0), @@ -185,6 +177,24 @@ class HistoryControlWidget extends StatelessWidget { } } + Widget _buildTime() { + List children = []; + children.add( + Text("${formatDate(selectedTimeStart, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}", textAlign: TextAlign.left,) + ); + if (selectedTimeEnd != null) { + children.add( + Text("${formatDate(selectedTimeEnd, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}", textAlign: TextAlign.left,) + ); + } + return Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children, + ), + ); + } + } class SimpleEntityStateHistoryMoment { diff --git a/lib/main.dart b/lib/main.dart index 06665dd..c4b24e9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -24,7 +24,8 @@ part 'entity_class/cover_entity.class.dart'; part 'entity_class/date_time_entity.class.dart'; part 'entity_class/light_entity.class.dart'; part 'entity_class/select_entity.class.dart'; -part 'entity_class/sun_entity.class.dart'; +part 'entity_class/other_entity.class.dart'; +part 'entity_class/slider_entity.dart'; part 'entity_widgets/badge.dart'; part 'entity_widgets/model_widgets.dart'; part 'entity_widgets/default_entity_container.dart'; @@ -37,6 +38,7 @@ part 'entity_widgets/mode_selector.dart'; part 'entity_widgets/entity_page_container.dart'; part 'entity_widgets/history_chart/entity_history.dart'; part 'entity_widgets/history_chart/simple_state_history_chart.dart'; +part 'entity_widgets/history_chart/numeric_state_history_chart.dart'; part 'entity_widgets/state/switch_state.dart'; part 'entity_widgets/state/slider_state.dart'; part 'entity_widgets/state/text_input_state.dart';