WIP #120 Numeric state charts
This commit is contained in:
parent
e16338c3f2
commit
fcd4ac7292
@ -5,15 +5,19 @@ class EntityColors {
|
|||||||
"on": Colors.amber,
|
"on": Colors.amber,
|
||||||
"auto": Colors.amber,
|
"auto": Colors.amber,
|
||||||
"idle": 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,
|
"playing": Colors.amber,
|
||||||
"above_horizon": Colors.amber,
|
"above_horizon": Colors.amber,
|
||||||
"home": 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) {
|
static Color stateColor(String state) {
|
||||||
@ -69,8 +73,8 @@ class Entity {
|
|||||||
DateTime _lastUpdated;
|
DateTime _lastUpdated;
|
||||||
|
|
||||||
List<Entity> childEntities = [];
|
List<Entity> childEntities = [];
|
||||||
|
|
||||||
List<String> attributesToShow = ["all"];
|
List<String> attributesToShow = ["all"];
|
||||||
|
int historyWidgetType = EntityHistoryWidgetType.simple;
|
||||||
|
|
||||||
String get displayName =>
|
String get displayName =>
|
||||||
attributes["friendly_name"] ?? (attributes["name"] ?? "_");
|
attributes["friendly_name"] ?? (attributes["name"] ?? "_");
|
||||||
@ -88,6 +92,7 @@ class Entity {
|
|||||||
List get childEntityIds => attributes["entity_id"] ?? [];
|
List get childEntityIds => attributes["entity_id"] ?? [];
|
||||||
String get lastUpdated => _getLastUpdatedFormatted();
|
String get lastUpdated => _getLastUpdatedFormatted();
|
||||||
bool get isHidden => attributes["hidden"] ?? false;
|
bool get isHidden => attributes["hidden"] ?? false;
|
||||||
|
double get doubleState => double.tryParse(state) ?? 0.0;
|
||||||
|
|
||||||
Entity(Map rawData) {
|
Entity(Map rawData) {
|
||||||
update(rawData);
|
update(rawData);
|
||||||
@ -167,7 +172,7 @@ class Entity {
|
|||||||
|
|
||||||
Widget buildHistoryWidget() {
|
Widget buildHistoryWidget() {
|
||||||
return EntityHistoryWidget(
|
return EntityHistoryWidget(
|
||||||
type: EntityHistoryWidgetType.simplest,
|
type: historyWidgetType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
lib/entity_class/other_entity.class.dart
Normal file
14
lib/entity_class/other_entity.class.dart
Normal 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);
|
||||||
|
|
||||||
|
}
|
@ -1,16 +1,11 @@
|
|||||||
part of '../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class SunEntity extends Entity {
|
|
||||||
SunEntity(Map rawData) : super(rawData);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SliderEntity extends Entity {
|
class SliderEntity extends Entity {
|
||||||
SliderEntity(Map rawData) : super(rawData);
|
SliderEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
double get minValue => attributes["min"] ?? 0.0;
|
double get minValue => _getDoubleAttributeValue("min") ?? 0.0;
|
||||||
double get maxValue => attributes["max"] ?? 100.0;
|
double get maxValue =>_getDoubleAttributeValue("max") ?? 100.0;
|
||||||
double get valueStep => attributes["step"] ?? 1.0;
|
double get valueStep => _getDoubleAttributeValue("step") ?? 1.0;
|
||||||
double get doubleState => double.tryParse(state) ?? 0.0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
@ -38,6 +38,9 @@ class EntityCollection {
|
|||||||
case 'sun': {
|
case 'sun': {
|
||||||
return SunEntity(rawEntityData);
|
return SunEntity(rawEntityData);
|
||||||
}
|
}
|
||||||
|
case 'sensor': {
|
||||||
|
return SensorEntity(rawEntityData);
|
||||||
|
}
|
||||||
case "automation":
|
case "automation":
|
||||||
case "input_boolean":
|
case "input_boolean":
|
||||||
case "switch": {
|
case "switch": {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
part of '../../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class EntityHistoryWidgetType {
|
class EntityHistoryWidgetType {
|
||||||
static const int simplest = 0;
|
static const int simple = 0;
|
||||||
static const int valueToTime = 1;
|
static const int valueToTime = 1;
|
||||||
|
static const int randomColors = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EntityHistoryWidget extends StatefulWidget {
|
class EntityHistoryWidget extends StatefulWidget {
|
||||||
@ -69,9 +70,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
children.add(
|
children.add(
|
||||||
SimpleStateHistoryChartWidget(
|
_selectChartWidget()
|
||||||
rawHistory: _history
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
children.add(Divider());
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
@ -162,15 +162,7 @@ class HistoryControlWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
_buildTime(),
|
||||||
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,),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.chevron_right),
|
icon: Icon(Icons.chevron_right),
|
||||||
padding: EdgeInsets.all(0.0),
|
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 {
|
class SimpleEntityStateHistoryMoment {
|
||||||
|
@ -24,7 +24,8 @@ part 'entity_class/cover_entity.class.dart';
|
|||||||
part 'entity_class/date_time_entity.class.dart';
|
part 'entity_class/date_time_entity.class.dart';
|
||||||
part 'entity_class/light_entity.class.dart';
|
part 'entity_class/light_entity.class.dart';
|
||||||
part 'entity_class/select_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/badge.dart';
|
||||||
part 'entity_widgets/model_widgets.dart';
|
part 'entity_widgets/model_widgets.dart';
|
||||||
part 'entity_widgets/default_entity_container.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/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/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';
|
||||||
|
Reference in New Issue
Block a user