WIP #120 Numeric state charts

This commit is contained in:
Yegor Vialov 2018-10-28 20:01:01 +02:00
parent e16338c3f2
commit fcd4ac7292
8 changed files with 221 additions and 30 deletions

View File

@ -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<Entity> childEntities = [];
List<String> 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,
);
}

View File

@ -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);
}

View File

@ -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) {

View File

@ -38,6 +38,9 @@ class EntityCollection {
case 'sun': {
return SunEntity(rawEntityData);
}
case 'sensor': {
return SensorEntity(rawEntityData);
}
case "automation":
case "input_boolean":
case "switch": {

View File

@ -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<EntityHistoryWidget> {
);
} else {
children.add(
SimpleStateHistoryChartWidget(
rawHistory: _history
)
_selectChartWidget()
);
}
children.add(Divider());
@ -83,4 +82,27 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
);
}
Widget _selectChartWidget() {
switch (widget.type) {
case EntityHistoryWidgetType.simple: {
return SimpleStateHistoryChartWidget(
rawHistory: _history,
);
}
case EntityHistoryWidgetType.valueToTime: {
return NumericStateHistoryChartWidget(
rawHistory: _history,
);
}
default: {
return SimpleStateHistoryChartWidget(
rawHistory: _history,
);
}
}
}
}

View File

@ -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<StatefulWidget> createState() {
return new _NumericStateHistoryChartWidgetState();
}
}
class _NumericStateHistoryChartWidgetState extends State<NumericStateHistoryChartWidget> {
int _selectedId = -1;
List<charts.Series<NumericEntityStateHistoryMoment, DateTime>> _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: <Widget>[
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<charts.Series<NumericEntityStateHistoryMoment, DateTime>> _parseHistory() {
List<NumericEntityStateHistoryMoment> 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<NumericEntityStateHistoryMoment, DateTime>(
id: 'State',
colorFn: (NumericEntityStateHistoryMoment historyMoment, __) => EntityColors.historyStateColor("unavailable"),
domainFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.time,
measureFn: (NumericEntityStateHistoryMoment historyMoment, _) => historyMoment.value,
data: data,
),
new charts.Series<NumericEntityStateHistoryMoment, DateTime>(
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);
}

View File

@ -162,15 +162,7 @@ class HistoryControlWidget extends StatelessWidget {
),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
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<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 Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
);
}
}
class SimpleEntityStateHistoryMoment {

View File

@ -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';