WIP #120 History chart based on attributes

This commit is contained in:
Yegor Vialov 2018-10-29 00:58:52 +02:00
parent c20200b609
commit 91db34badb
9 changed files with 327 additions and 21 deletions

View File

@ -4,6 +4,13 @@ class ClimateEntity extends Entity {
@override @override
double widgetHeight = 38.0; double widgetHeight = 38.0;
@override
EntityHistoryConfig historyConfig = EntityHistoryConfig(
chartType: EntityHistoryWidgetType.numericAttributes,
numericState: false,
numericAttributesToShow: ["temperature", "current_temperature"]
);
static const SUPPORT_TARGET_TEMPERATURE = 1; static const SUPPORT_TARGET_TEMPERATURE = 1;
static const SUPPORT_TARGET_TEMPERATURE_HIGH = 2; static const SUPPORT_TARGET_TEMPERATURE_HIGH = 2;
static const SUPPORT_TARGET_TEMPERATURE_LOW = 4; static const SUPPORT_TARGET_TEMPERATURE_LOW = 4;

View File

@ -38,7 +38,9 @@ class Entity {
List<Entity> childEntities = []; List<Entity> childEntities = [];
List<String> attributesToShow = ["all"]; List<String> attributesToShow = ["all"];
int historyWidgetType = EntityHistoryWidgetType.simple; EntityHistoryConfig historyConfig = EntityHistoryConfig(
chartType: EntityHistoryWidgetType.simple
);
String get displayName => String get displayName =>
attributes["friendly_name"] ?? (attributes["name"] ?? "_"); attributes["friendly_name"] ?? (attributes["name"] ?? "_");
@ -136,7 +138,7 @@ class Entity {
Widget buildHistoryWidget() { Widget buildHistoryWidget() {
return EntityHistoryWidget( return EntityHistoryWidget(
type: historyWidgetType, config: historyConfig,
); );
} }

View File

@ -7,7 +7,10 @@ class SunEntity extends Entity {
class SensorEntity extends Entity { class SensorEntity extends Entity {
@override @override
int historyWidgetType = EntityHistoryWidgetType.valueToTime; EntityHistoryConfig historyConfig = EntityHistoryConfig(
chartType: EntityHistoryWidgetType.numericState,
numericState: true
);
SensorEntity(Map rawData) : super(rawData); SensorEntity(Map rawData) : super(rawData);

View File

@ -48,7 +48,7 @@ class EntityColors {
charts.Color c1 = charts.MaterialPalette.getOrderedPalettes(id + 1)[id].shadeDefault; charts.Color c1 = charts.MaterialPalette.getOrderedPalettes(id + 1)[id].shadeDefault;
return Color.fromARGB(c1.a, c1.r, c1.g, c1.b); return Color.fromARGB(c1.a, c1.r, c1.g, c1.b);
} else { } else {
return _stateColors["default"]; return _stateColors["on"];
} }
} }
} }

View File

@ -0,0 +1,264 @@
part of '../../main.dart';
class CombinedHistoryChartWidget extends StatefulWidget {
final rawHistory;
final EntityHistoryConfig config;
const CombinedHistoryChartWidget({Key key, @required this.rawHistory, @required this.config}) : super(key: key);
@override
State<StatefulWidget> createState() {
return new _CombinedHistoryChartWidgetState();
}
}
class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget> {
int _selectedId = -1;
List<charts.Series<CombinedEntityStateHistoryMoment, DateTime>> _parsedHistory;
@override
Widget build(BuildContext context) {
_parsedHistory = _parseHistory();
DateTime selectedTime;
List<String> selectedStates = [];
List<int> colorIndexes = [];
if ((_selectedId > -1) && (_parsedHistory != null) && (_parsedHistory.first.data.length >= (_selectedId + 1))) {
selectedTime = _parsedHistory.first.data[_selectedId].time;
_parsedHistory.where((item) { return item.id == "value"; }).forEach((item) {
selectedStates.add("${item.data[_selectedId].value}");
colorIndexes.add(item.data[_selectedId].colorId);
});
}
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
CombinedHistoryControlWidget(
selectedTimeStart: selectedTime,
selectedStates: selectedStates,
onPrevTap: () => _selectPrev(),
onNextTap: () => _selectNext(),
colorIndexes: colorIndexes,
),
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(includeArea: false),
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(),
],
),
)
],
);
}
double _parseToDouble(temp1) {
if (temp1 is int) {
return temp1.toDouble();
} else if (temp1 is double) {
return temp1;
} else {
return double.tryParse("$temp1");
}
}
List<charts.Series<CombinedEntityStateHistoryMoment, DateTime>> _parseHistory() {
TheLogger.debug(" parsing history...");
Map<String, List<CombinedEntityStateHistoryMoment>> dataList = {};
int colorIdCounter = 0;
widget.config.numericAttributesToShow.forEach((String attrName) {
TheLogger.debug(" parsing attribute $attrName");
List<CombinedEntityStateHistoryMoment> 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();
if (stateData["attributes"] != null) {
data.add(CombinedEntityStateHistoryMoment(_parseToDouble(stateData["attributes"]["$attrName"]), stateData["state"], time, i, colorIdCounter));
} else {
data.add(CombinedEntityStateHistoryMoment(null, stateData["state"], time, i, colorIdCounter));
}
}
data.add(CombinedEntityStateHistoryMoment(data.last.value, data.last.state, now, widget.rawHistory.length, colorIdCounter));
dataList.addAll({attrName: data});
colorIdCounter += 1;
});
if ((_selectedId == -1) && (dataList.isNotEmpty)) {
_selectedId = 0;
}
List<charts.Series<CombinedEntityStateHistoryMoment, DateTime>> result = [];
dataList.forEach((attrName, dataItem) {
TheLogger.debug(" adding ${dataItem.length} data values");
result.addAll([
new charts.Series<CombinedEntityStateHistoryMoment, DateTime>(
id: "value",
colorFn: (CombinedEntityStateHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("_", historyMoment.colorId),
domainFn: (CombinedEntityStateHistoryMoment historyMoment, _) => historyMoment.time,
measureFn: (CombinedEntityStateHistoryMoment historyMoment, _) => historyMoment.value,
data: dataItem,
),
new charts.Series<CombinedEntityStateHistoryMoment, DateTime>(
id: "points",
radiusPxFn: (CombinedEntityStateHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 1.0,
colorFn: (CombinedEntityStateHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("_", historyMoment.colorId),
domainFn: (CombinedEntityStateHistoryMoment historyMoment, _) => historyMoment.time,
measureFn: (CombinedEntityStateHistoryMoment historyMoment, _) => historyMoment.value,
data: dataItem,
)..setAttribute(charts.rendererIdKey, 'valuePoints')
]);
});
return result;
}
void _selectPrev() {
if (_selectedId > 0) {
setState(() {
_selectedId -= 1;
});
}
}
void _selectNext() {
if (_selectedId < (_parsedHistory.first.data.length - 1)) {
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 CombinedHistoryControlWidget extends StatelessWidget {
final Function onPrevTap;
final Function onNextTap;
final DateTime selectedTimeStart;
final DateTime selectedTimeEnd;
final List<String> selectedStates;
final List<int> colorIndexes;
const CombinedHistoryControlWidget({Key key, this.onPrevTap, this.onNextTap, this.selectedTimeStart, this.selectedTimeEnd, this.selectedStates, @ required this.colorIndexes}) : super(key: key);
@override
Widget build(BuildContext context) {
if (selectedTimeStart != null) {
return
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
IconButton(
icon: Icon(Icons.chevron_left),
padding: EdgeInsets.all(0.0),
iconSize: 40.0,
onPressed: onPrevTap,
),
Expanded(
child: Padding(
padding: EdgeInsets.only(right: 10.0),
child: _buildStates(),
),
),
_buildTime(),
IconButton(
icon: Icon(Icons.chevron_right),
padding: EdgeInsets.all(0.0),
iconSize: 40.0,
onPressed: onNextTap,
),
],
);
} else {
return Container(height: 48.0);
}
}
Widget _buildStates() {
List<Widget> children = [];
for (int i = 0; i < selectedStates.length; i++) {
children.add(
Text(
"${selectedStates[i]}",
textAlign: TextAlign.right,
style: TextStyle(
fontWeight: FontWeight.bold,
color: EntityColors.historyStateColor(selectedStates[i], colorIndexes[i]),
fontSize: 22.0
),
)
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: children,
);
}
Widget _buildTime() {
List<Widget> 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 Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
);
}
}
class CombinedEntityStateHistoryMoment {
final DateTime time;
final double value;
final int id;
final int colorId;
final String state;
CombinedEntityStateHistoryMoment(this.value, this.state, this.time, this.id, this.colorId);
}

View File

@ -2,15 +2,24 @@ part of '../../main.dart';
class EntityHistoryWidgetType { class EntityHistoryWidgetType {
static const int simple = 0; static const int simple = 0;
static const int valueToTime = 1; static const int numericState = 1;
static const int randomColors = 2; static const int numericAttributes = 2;
}
class EntityHistoryConfig {
final int chartType;
final List<String> numericAttributesToShow;
final bool numericState;
EntityHistoryConfig({this.chartType, this.numericAttributesToShow, this.numericState: true});
} }
class EntityHistoryWidget extends StatefulWidget { class EntityHistoryWidget extends StatefulWidget {
final int type; final EntityHistoryConfig config;
const EntityHistoryWidget({Key key, @required this.type}) : super(key: key); const EntityHistoryWidget({Key key, @required this.config}) : super(key: key);
@override @override
_EntityHistoryWidgetState createState() { _EntityHistoryWidgetState createState() {
@ -74,7 +83,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
} }
children.add(Divider()); children.add(Divider());
return Padding( return Padding(
padding: EdgeInsets.fromLTRB(0.0, Entity.rowPadding, 0.0, Entity.rowPadding), padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, Entity.rowPadding),
child: Column( child: Column(
children: children, children: children,
), ),
@ -82,20 +91,34 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
} }
Widget _selectChartWidget() { Widget _selectChartWidget() {
switch (widget.type) { TheLogger.debug(" selecting history widget (${widget.config.chartType})");
switch (widget.config.chartType) {
case EntityHistoryWidgetType.simple: { case EntityHistoryWidgetType.simple: {
TheLogger.debug(" Simple selected");
return SimpleStateHistoryChartWidget( return SimpleStateHistoryChartWidget(
rawHistory: _history, rawHistory: _history,
); );
} }
case EntityHistoryWidgetType.valueToTime: { case EntityHistoryWidgetType.numericState: {
TheLogger.debug(" EntityHistory selected");
return NumericStateHistoryChartWidget( return NumericStateHistoryChartWidget(
rawHistory: _history, rawHistory: _history,
config: widget.config,
);
}
case EntityHistoryWidgetType.numericAttributes: {
TheLogger.debug(" NumericAttributes selected");
return CombinedHistoryChartWidget(
rawHistory: _history,
config: widget.config,
); );
} }
default: { default: {
TheLogger.debug(" Simple selected as default");
return SimpleStateHistoryChartWidget( return SimpleStateHistoryChartWidget(
rawHistory: _history, rawHistory: _history,
); );

View File

@ -2,8 +2,9 @@ part of '../../main.dart';
class NumericStateHistoryChartWidget extends StatefulWidget { class NumericStateHistoryChartWidget extends StatefulWidget {
final rawHistory; final rawHistory;
final EntityHistoryConfig config;
const NumericStateHistoryChartWidget({Key key, this.rawHistory}) : super(key: key); const NumericStateHistoryChartWidget({Key key, @required this.rawHistory, @required this.config}) : super(key: key);
@override @override
@ -86,15 +87,15 @@ class _NumericStateHistoryChartWidgetState extends State<NumericStateHistoryChar
return [ return [
new charts.Series<NumericEntityStateHistoryMoment, DateTime>( new charts.Series<NumericEntityStateHistoryMoment, DateTime>(
id: 'State', id: 'State',
colorFn: (NumericEntityStateHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("unavailable", historyMoment.id), colorFn: (NumericEntityStateHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("on", -1),
domainFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.time, domainFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.time,
measureFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.value, measureFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.value,
data: data, data: data,
), ),
new charts.Series<NumericEntityStateHistoryMoment, DateTime>( new charts.Series<NumericEntityStateHistoryMoment, DateTime>(
id: 'State', id: 'State',
radiusPxFn: (NumericEntityStateHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 2.0, radiusPxFn: (NumericEntityStateHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 1.0,
colorFn: (NumericEntityStateHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("off", historyMoment.id), colorFn: (NumericEntityStateHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("on", -1),
domainFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.time, domainFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.time,
measureFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.value, measureFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.value,
data: data, data: data,
@ -111,7 +112,7 @@ class _NumericStateHistoryChartWidgetState extends State<NumericStateHistoryChar
} }
void _selectNext() { void _selectNext() {
if (_selectedId < (_parsedHistory.first.data.length - 2)) { if (_selectedId < (_parsedHistory.first.data.length - 1)) {
setState(() { setState(() {
_selectedId += 1; _selectedId += 1;
}); });

View File

@ -39,7 +39,7 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
selectedState: selectedState, selectedState: selectedState,
onPrevTap: () => _selectPrev(), onPrevTap: () => _selectPrev(),
onNextTap: () => _selectNext(), onNextTap: () => _selectNext(),
colorIndex: _selectedId, colorIndex: _parsedHistory.first.data[_selectedId].colorId,
), ),
SizedBox( SizedBox(
height: 70.0, height: 70.0,
@ -68,6 +68,7 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
List<charts.Series<SimpleEntityStateHistoryMoment, DateTime>> _parseHistory() { List<charts.Series<SimpleEntityStateHistoryMoment, DateTime>> _parseHistory() {
List<SimpleEntityStateHistoryMoment> data = []; List<SimpleEntityStateHistoryMoment> data = [];
DateTime now = DateTime.now(); DateTime now = DateTime.now();
Map<String, int> cachedStates = {};
for (var i = 0; i < widget.rawHistory.length; i++) { for (var i = 0; i < widget.rawHistory.length; i++) {
var stateData = widget.rawHistory[i]; var stateData = widget.rawHistory[i];
DateTime startTime = DateTime.tryParse(stateData["last_updated"])?.toLocal(); DateTime startTime = DateTime.tryParse(stateData["last_updated"])?.toLocal();
@ -77,9 +78,12 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
} else { } else {
endTime = now; endTime = now;
} }
data.add(SimpleEntityStateHistoryMoment(stateData["state"], startTime, endTime, i)); if (cachedStates[stateData["state"]] == null) {
cachedStates.addAll({"${stateData["state"]}": cachedStates.length});
} }
data.add(SimpleEntityStateHistoryMoment(data.last.state, now, null, widget.rawHistory.length)); data.add(SimpleEntityStateHistoryMoment(stateData["state"], startTime, endTime, i, cachedStates[stateData["state"]]));
}
data.add(SimpleEntityStateHistoryMoment(data.last.state, now, null, widget.rawHistory.length, data.last.colorId));
if (_selectedId == -1) { if (_selectedId == -1) {
_selectedId = 0; _selectedId = 0;
} }
@ -87,7 +91,7 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
new charts.Series<SimpleEntityStateHistoryMoment, DateTime>( new charts.Series<SimpleEntityStateHistoryMoment, DateTime>(
id: 'State', id: 'State',
strokeWidthPxFn: (SimpleEntityStateHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 70.0 : 40.0, strokeWidthPxFn: (SimpleEntityStateHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 70.0 : 40.0,
colorFn: (SimpleEntityStateHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.id), colorFn: (SimpleEntityStateHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
domainFn: (SimpleEntityStateHistoryMoment historyMoment, _) => historyMoment.startTime, domainFn: (SimpleEntityStateHistoryMoment historyMoment, _) => historyMoment.startTime,
measureFn: (SimpleEntityStateHistoryMoment historyMoment, _) => 0, measureFn: (SimpleEntityStateHistoryMoment historyMoment, _) => 0,
data: data, data: data,
@ -205,6 +209,7 @@ class SimpleEntityStateHistoryMoment {
final DateTime endTime; final DateTime endTime;
final String state; final String state;
final int id; final int id;
final int colorId;
SimpleEntityStateHistoryMoment(this.state, this.startTime, this.endTime, this.id); SimpleEntityStateHistoryMoment(this.state, this.startTime, this.endTime, this.id, this.colorId);
} }

View File

@ -41,6 +41,7 @@ part 'entity_widgets/entity_page_container.dart';
part 'entity_widgets/history_chart/entity_history.dart'; part 'entity_widgets/history_chart/entity_history.dart';
part 'entity_widgets/history_chart/simple_state_history_chart.dart'; part 'entity_widgets/history_chart/simple_state_history_chart.dart';
part 'entity_widgets/history_chart/numeric_state_history_chart.dart'; part 'entity_widgets/history_chart/numeric_state_history_chart.dart';
part 'entity_widgets/history_chart/combined_history_chart.dart';
part 'entity_widgets/state/switch_state.dart'; part 'entity_widgets/state/switch_state.dart';
part 'entity_widgets/state/slider_state.dart'; part 'entity_widgets/state/slider_state.dart';
part 'entity_widgets/state/text_input_state.dart'; part 'entity_widgets/state/text_input_state.dart';