Project structure changes
This commit is contained in:
241
lib/plugins/history_chart/combined_history_chart.dart
Normal file
241
lib/plugins/history_chart/combined_history_chart.dart
Normal file
@ -0,0 +1,241 @@
|
||||
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<EntityHistoryMoment, DateTime>> _parsedHistory;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@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].startTime;
|
||||
_parsedHistory.where((item) { return item.id == "state"; }).forEach((item) {
|
||||
selectedStates.add(item.data[_selectedId].state);
|
||||
colorIndexes.add(item.data[_selectedId].colorId);
|
||||
});
|
||||
_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>[
|
||||
HistoryControlWidget(
|
||||
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,
|
||||
includePoints: true
|
||||
),
|
||||
selectionModels: [
|
||||
new charts.SelectionModelConfig(
|
||||
type: charts.SelectionModelType.info,
|
||||
changedListener: (model) => _onSelectionChanged(model),
|
||||
)
|
||||
],
|
||||
customSeriesRenderers: [
|
||||
new charts.SymbolAnnotationRendererConfig(
|
||||
customRendererId: "stateBars"
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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<EntityHistoryMoment, DateTime>> _parseHistory() {
|
||||
Logger.d(" parsing history...");
|
||||
Map<String, List<EntityHistoryMoment>> numericDataLists = {};
|
||||
int colorIdCounter = 0;
|
||||
widget.config.numericAttributesToShow.forEach((String attrName) {
|
||||
Logger.d(" parsing attribute $attrName");
|
||||
List<EntityHistoryMoment> data = [];
|
||||
DateTime now = DateTime.now();
|
||||
for (var i = 0; i < widget.rawHistory.length; i++) {
|
||||
var stateData = widget.rawHistory[i];
|
||||
DateTime startTime = DateTime.tryParse(stateData["last_updated"])?.toLocal();
|
||||
DateTime endTime;
|
||||
bool hiddenLine;
|
||||
double value;
|
||||
double previousValue = 0.0;
|
||||
value = _parseToDouble(stateData["attributes"]["$attrName"]);
|
||||
bool hiddenDot = (value == null);
|
||||
if (hiddenDot && i > 0) {
|
||||
previousValue = data[i-1].value ?? data[i-1].previousValue;
|
||||
}
|
||||
if (i < (widget.rawHistory.length - 1)) {
|
||||
endTime = DateTime.tryParse(widget.rawHistory[i+1]["last_updated"])?.toLocal();
|
||||
double nextValue = _parseToDouble(widget.rawHistory[i+1]["attributes"]["$attrName"]);
|
||||
hiddenLine = (nextValue == null || hiddenDot);
|
||||
} else {
|
||||
hiddenLine = hiddenDot;
|
||||
endTime = now;
|
||||
}
|
||||
data.add(EntityHistoryMoment(
|
||||
value: value,
|
||||
previousValue: previousValue,
|
||||
hiddenDot: hiddenDot,
|
||||
hiddenLine: hiddenLine,
|
||||
state: stateData["state"],
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
id: i,
|
||||
colorId: colorIdCounter
|
||||
));
|
||||
}
|
||||
data.add(EntityHistoryMoment(
|
||||
value: data.last.value,
|
||||
previousValue: data.last.previousValue,
|
||||
hiddenDot: data.last.hiddenDot,
|
||||
hiddenLine: data.last.hiddenLine,
|
||||
state: data.last.state,
|
||||
startTime: now,
|
||||
id: widget.rawHistory.length,
|
||||
colorId: colorIdCounter
|
||||
));
|
||||
numericDataLists.addAll({attrName: data});
|
||||
colorIdCounter += 1;
|
||||
});
|
||||
|
||||
if ((_selectedId == -1) && (numericDataLists.isNotEmpty)) {
|
||||
_selectedId = numericDataLists.length -1;
|
||||
}
|
||||
List<charts.Series<EntityHistoryMoment, DateTime>> result = [];
|
||||
numericDataLists.forEach((attrName, dataList) {
|
||||
Logger.d(" adding ${dataList.length} data values");
|
||||
result.add(
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: "value",
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor("_", historyMoment.colorId),
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) {
|
||||
if (historyMoment.hiddenDot) {
|
||||
return 0.0;
|
||||
} else if (historyMoment.id == _selectedId) {
|
||||
return 5.0;
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
},
|
||||
strokeWidthPxFn: (EntityHistoryMoment historyMoment, __) => historyMoment.hiddenLine ? 0.0 : 2.0,
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => historyMoment.value ?? historyMoment.previousValue,
|
||||
data: dataList,
|
||||
/*domainLowerBoundFn: (CombinedEntityStateHistoryMoment historyMoment, _) => historyMoment.time.subtract(Duration(hours: 1)),
|
||||
domainUpperBoundFn: (CombinedEntityStateHistoryMoment historyMoment, _) => historyMoment.time.add(Duration(hours: 1)),*/
|
||||
)
|
||||
);
|
||||
});
|
||||
result.add(
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'state',
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 4.0,
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
domainLowerBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
domainUpperBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
||||
// No measure values are needed for symbol annotations.
|
||||
measureFn: (_, __) => null,
|
||||
data: numericDataLists[numericDataLists.keys.first],
|
||||
)
|
||||
// Configure our custom symbol annotation renderer for this series.
|
||||
..setAttribute(charts.rendererIdKey, 'stateBars')
|
||||
// Optional radius for the annotation shape. If not specified, this will
|
||||
// default to the same radius as the points.
|
||||
//..setAttribute(charts.boundsLineRadiusPxKey, 3.5)
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
void _selectPrev() {
|
||||
if (_selectedId > 0) {
|
||||
setState(() {
|
||||
_selectedId -= 1;
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
_selectedId = _parsedHistory.first.data.length - 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _selectNext() {
|
||||
if (_selectedId < (_parsedHistory.first.data.length - 1)) {
|
||||
setState(() {
|
||||
_selectedId += 1;
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
_selectedId = 0;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectionChanged(charts.SelectionModel model) {
|
||||
final selectedDatum = model.selectedDatum;
|
||||
|
||||
int selectedId;
|
||||
|
||||
if (selectedDatum.isNotEmpty) {
|
||||
selectedId = selectedDatum.first.datum.id;
|
||||
setState(() {
|
||||
_selectedId = selectedId;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
144
lib/plugins/history_chart/entity_history.dart
Normal file
144
lib/plugins/history_chart/entity_history.dart
Normal file
@ -0,0 +1,144 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class EntityHistoryWidgetType {
|
||||
static const int simple = 0;
|
||||
static const int numericState = 1;
|
||||
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 {
|
||||
|
||||
final EntityHistoryConfig config;
|
||||
|
||||
const EntityHistoryWidget({Key key, @required this.config}) : super(key: key);
|
||||
|
||||
@override
|
||||
_EntityHistoryWidgetState createState() {
|
||||
return new _EntityHistoryWidgetState();
|
||||
}
|
||||
}
|
||||
|
||||
class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
|
||||
List _history;
|
||||
bool _needToUpdateHistory;
|
||||
DateTime _historyLastUpdated;
|
||||
bool _disposed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_needToUpdateHistory = true;
|
||||
}
|
||||
|
||||
void _loadHistory(String entityId) {
|
||||
DateTime now = DateTime.now();
|
||||
if (_historyLastUpdated != null) {
|
||||
Logger.d("History was updated ${now.difference(_historyLastUpdated).inSeconds} seconds ago");
|
||||
}
|
||||
if (_historyLastUpdated == null || now.difference(_historyLastUpdated).inSeconds > 30) {
|
||||
_historyLastUpdated = now;
|
||||
ConnectionManager().getHistory(entityId).then((history){
|
||||
if (!_disposed) {
|
||||
setState(() {
|
||||
_history = history.isNotEmpty ? history[0] : [];
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
}
|
||||
}).catchError((e) {
|
||||
Logger.e("Error loading $entityId history: $e");
|
||||
if (!_disposed) {
|
||||
setState(() {
|
||||
_history = [];
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final Entity entity = entityModel.entityWrapper.entity;
|
||||
if (!_needToUpdateHistory) {
|
||||
_needToUpdateHistory = true;
|
||||
} else {
|
||||
_loadHistory(entity.entityId);
|
||||
}
|
||||
return _buildChart();
|
||||
}
|
||||
|
||||
Widget _buildChart() {
|
||||
List<Widget> children = [];
|
||||
if (_history == null) {
|
||||
children.add(
|
||||
Text("Loading history...")
|
||||
);
|
||||
} else if (_history.isEmpty) {
|
||||
children.add(
|
||||
Text("No history")
|
||||
);
|
||||
} else {
|
||||
children.add(
|
||||
_selectChartWidget()
|
||||
);
|
||||
}
|
||||
children.add(Divider());
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, Sizes.rowPadding),
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _selectChartWidget() {
|
||||
switch (widget.config.chartType) {
|
||||
|
||||
case EntityHistoryWidgetType.simple: {
|
||||
return SimpleStateHistoryChartWidget(
|
||||
rawHistory: _history,
|
||||
);
|
||||
}
|
||||
|
||||
case EntityHistoryWidgetType.numericState: {
|
||||
return NumericStateHistoryChartWidget(
|
||||
rawHistory: _history,
|
||||
config: widget.config,
|
||||
);
|
||||
}
|
||||
|
||||
case EntityHistoryWidgetType.numericAttributes: {
|
||||
return CombinedHistoryChartWidget(
|
||||
rawHistory: _history,
|
||||
config: widget.config,
|
||||
);
|
||||
}
|
||||
|
||||
default: {
|
||||
Logger.d(" Simple selected as default");
|
||||
return SimpleStateHistoryChartWidget(
|
||||
rawHistory: _history,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
25
lib/plugins/history_chart/entity_history_moment.dart
Normal file
25
lib/plugins/history_chart/entity_history_moment.dart
Normal file
@ -0,0 +1,25 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class EntityHistoryMoment {
|
||||
final DateTime startTime;
|
||||
final DateTime endTime;
|
||||
final double value;
|
||||
final double previousValue;
|
||||
final int id;
|
||||
final int colorId;
|
||||
final String state;
|
||||
final bool hiddenDot;
|
||||
final bool hiddenLine;
|
||||
|
||||
EntityHistoryMoment({
|
||||
this.value,
|
||||
this.previousValue,
|
||||
this.hiddenDot,
|
||||
this.hiddenLine,
|
||||
this.state,
|
||||
@required this.startTime,
|
||||
this.endTime,
|
||||
@required this.id,
|
||||
this.colorId
|
||||
});
|
||||
}
|
86
lib/plugins/history_chart/history_control_widget.dart
Normal file
86
lib/plugins/history_chart/history_control_widget.dart
Normal file
@ -0,0 +1,86 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class HistoryControlWidget extends StatelessWidget {
|
||||
|
||||
final Function onPrevTap;
|
||||
final Function onNextTap;
|
||||
final DateTime selectedTimeStart;
|
||||
final DateTime selectedTimeEnd;
|
||||
final List<String> selectedStates;
|
||||
final List<int> colorIndexes;
|
||||
|
||||
const HistoryControlWidget({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: EntityColor.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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
171
lib/plugins/history_chart/numeric_state_history_chart.dart
Normal file
171
lib/plugins/history_chart/numeric_state_history_chart.dart
Normal file
@ -0,0 +1,171 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class NumericStateHistoryChartWidget extends StatefulWidget {
|
||||
final rawHistory;
|
||||
final EntityHistoryConfig config;
|
||||
|
||||
const NumericStateHistoryChartWidget({Key key, @required this.rawHistory, @required this.config}) : super(key: key);
|
||||
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return new _NumericStateHistoryChartWidgetState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class _NumericStateHistoryChartWidgetState extends State<NumericStateHistoryChartWidget> {
|
||||
|
||||
int _selectedId = -1;
|
||||
List<charts.Series<EntityHistoryMoment, 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].startTime;
|
||||
selectedState = _parsedHistory.first.data[_selectedId].value;
|
||||
}
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
HistoryControlWidget(
|
||||
selectedTimeStart: selectedTime,
|
||||
selectedStates: ["${selectedState ?? '-'}"],
|
||||
onPrevTap: () => _selectPrev(),
|
||||
onNextTap: () => _selectNext(),
|
||||
colorIndexes: [-1],
|
||||
),
|
||||
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(
|
||||
includePoints: true
|
||||
),
|
||||
/*primaryMeasureAxis: charts.NumericAxisSpec(
|
||||
renderSpec: charts.NoneRenderSpec()
|
||||
),*/
|
||||
selectionModels: [
|
||||
new charts.SelectionModelConfig(
|
||||
type: charts.SelectionModelType.info,
|
||||
changedListener: (model) => _onSelectionChanged(model),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<charts.Series<EntityHistoryMoment, DateTime>> _parseHistory() {
|
||||
List<EntityHistoryMoment> 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();
|
||||
double value = double.tryParse(stateData["state"]);
|
||||
double previousValue = 0.0;
|
||||
bool hiddenDot = (value == null);
|
||||
bool hiddenLine;
|
||||
if (hiddenDot && i > 0) {
|
||||
previousValue = data[i-1].value ?? data[i-1].previousValue;
|
||||
}
|
||||
if (i < (widget.rawHistory.length - 1)) {
|
||||
double nextValue = double.tryParse(widget.rawHistory[i+1]["state"]);
|
||||
hiddenLine = (nextValue == null || hiddenDot);
|
||||
} else {
|
||||
hiddenLine = hiddenDot;
|
||||
}
|
||||
data.add(EntityHistoryMoment(
|
||||
value: value,
|
||||
previousValue: previousValue,
|
||||
hiddenDot: hiddenDot,
|
||||
hiddenLine: hiddenLine,
|
||||
startTime: time,
|
||||
id: i
|
||||
));
|
||||
}
|
||||
data.add(EntityHistoryMoment(
|
||||
value: data.last.value,
|
||||
previousValue: data.last.previousValue,
|
||||
hiddenDot: data.last.hiddenDot,
|
||||
hiddenLine: data.last.hiddenLine,
|
||||
startTime: now,
|
||||
id: widget.rawHistory.length
|
||||
));
|
||||
if (_selectedId == -1) {
|
||||
_selectedId = data.length - 1;
|
||||
}
|
||||
return [
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(EntityState.on, -1),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => historyMoment.value ?? historyMoment.previousValue,
|
||||
data: data,
|
||||
strokeWidthPxFn: (EntityHistoryMoment historyMoment, __) => historyMoment.hiddenLine ? 0.0 : 2.0,
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) {
|
||||
if (historyMoment.hiddenDot) {
|
||||
return 0.0;
|
||||
} else if (historyMoment.id == _selectedId) {
|
||||
return 5.0;
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
},
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
void _selectPrev() {
|
||||
if (_selectedId > 0) {
|
||||
setState(() {
|
||||
_selectedId -= 1;
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
_selectedId = _parsedHistory.first.data.length - 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _selectNext() {
|
||||
if (_selectedId < (_parsedHistory.first.data.length - 1)) {
|
||||
setState(() {
|
||||
_selectedId += 1;
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
_selectedId = 0;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectionChanged(charts.SelectionModel model) {
|
||||
final selectedDatum = model.selectedDatum;
|
||||
|
||||
int selectedId;
|
||||
|
||||
if (selectedDatum.isNotEmpty) {
|
||||
selectedId = selectedDatum.first.datum.id;
|
||||
setState(() {
|
||||
_selectedId = selectedId;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
187
lib/plugins/history_chart/simple_state_history_chart.dart
Normal file
187
lib/plugins/history_chart/simple_state_history_chart.dart
Normal file
@ -0,0 +1,187 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SimpleStateHistoryChartWidget extends StatefulWidget {
|
||||
final rawHistory;
|
||||
|
||||
const SimpleStateHistoryChartWidget({Key key, this.rawHistory}) : super(key: key);
|
||||
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return new _SimpleStateHistoryChartWidgetState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartWidget> {
|
||||
|
||||
int _selectedId = -1;
|
||||
List<charts.Series<EntityHistoryMoment, DateTime>> _parsedHistory;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_parsedHistory = _parseHistory();
|
||||
DateTime selectedTimeStart;
|
||||
DateTime selectedTimeEnd;
|
||||
String selectedState;
|
||||
if ((_selectedId > -1) && (_parsedHistory != null) && (_parsedHistory.first.data.length >= (_selectedId + 1))) {
|
||||
selectedTimeStart = _parsedHistory.first.data[_selectedId].startTime;
|
||||
selectedTimeEnd = _parsedHistory.first.data[_selectedId].endTime;
|
||||
selectedState = _parsedHistory.first.data[_selectedId].state;
|
||||
}
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
HistoryControlWidget(
|
||||
selectedTimeStart: selectedTimeStart,
|
||||
selectedTimeEnd: selectedTimeEnd,
|
||||
selectedStates: [selectedState],
|
||||
onPrevTap: () => _selectPrev(),
|
||||
onNextTap: () => _selectNext(),
|
||||
colorIndexes: [_parsedHistory.first.data[_selectedId].colorId],
|
||||
),
|
||||
SizedBox(
|
||||
height: 70.0,
|
||||
child: charts.TimeSeriesChart(
|
||||
_parsedHistory,
|
||||
animate: false,
|
||||
dateTimeFactory: const charts.LocalDateTimeFactory(),
|
||||
primaryMeasureAxis: charts.NumericAxisSpec(
|
||||
renderSpec: charts.NoneRenderSpec()
|
||||
),
|
||||
selectionModels: [
|
||||
new charts.SelectionModelConfig(
|
||||
type: charts.SelectionModelType.info,
|
||||
changedListener: (model) => _onSelectionChanged(model),
|
||||
)
|
||||
],
|
||||
customSeriesRenderers: [
|
||||
new charts.PointRendererConfig(
|
||||
// ID used to link series to this renderer.
|
||||
customRendererId: 'startValuePoints'),
|
||||
new charts.PointRendererConfig(
|
||||
// ID used to link series to this renderer.
|
||||
customRendererId: 'endValuePoints')
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<charts.Series<EntityHistoryMoment, DateTime>> _parseHistory() {
|
||||
List<EntityHistoryMoment> data = [];
|
||||
DateTime now = DateTime.now();
|
||||
Map<String, int> cachedStates = {};
|
||||
for (var i = 0; i < widget.rawHistory.length; i++) {
|
||||
var stateData = widget.rawHistory[i];
|
||||
DateTime startTime = DateTime.tryParse(stateData["last_updated"])?.toLocal();
|
||||
DateTime endTime;
|
||||
if (i < (widget.rawHistory.length - 1)) {
|
||||
endTime = DateTime.tryParse(widget.rawHistory[i+1]["last_updated"])?.toLocal();
|
||||
} else {
|
||||
endTime = now;
|
||||
}
|
||||
if (cachedStates[stateData["state"]] == null) {
|
||||
cachedStates.addAll({"${stateData["state"]}": cachedStates.length});
|
||||
}
|
||||
data.add(EntityHistoryMoment(
|
||||
state: stateData["state"],
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
id: i,
|
||||
colorId: cachedStates[stateData["state"]]
|
||||
));
|
||||
}
|
||||
data.add(EntityHistoryMoment(
|
||||
state: data.last.state,
|
||||
startTime: now,
|
||||
id: widget.rawHistory.length,
|
||||
colorId: data.last.colorId
|
||||
));
|
||||
if (_selectedId == -1) {
|
||||
_selectedId = data.length - 1;
|
||||
}
|
||||
return [
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
strokeWidthPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 6.0 : 3.0,
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||
data: data,
|
||||
),
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||
data: data,
|
||||
)..setAttribute(charts.rendererIdKey, 'startValuePoints'),
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||
data: data,
|
||||
)..setAttribute(charts.rendererIdKey, 'endValuePoints')
|
||||
];
|
||||
}
|
||||
|
||||
void _selectPrev() {
|
||||
if (_selectedId > 0) {
|
||||
setState(() {
|
||||
_selectedId -= 1;
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
_selectedId = _parsedHistory.first.data.length - 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _selectNext() {
|
||||
if (_selectedId < (_parsedHistory.first.data.length - 1)) {
|
||||
setState(() {
|
||||
_selectedId += 1;
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
_selectedId = 0;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectionChanged(charts.SelectionModel model) {
|
||||
final selectedDatum = model.selectedDatum;
|
||||
|
||||
int selectedId;
|
||||
|
||||
if ((selectedDatum.isNotEmpty) &&(selectedDatum.first.datum.endTime != null)) {
|
||||
selectedId = selectedDatum.first.datum.id;
|
||||
setState(() {
|
||||
_selectedId = selectedId;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class SimpleEntityStateHistoryMoment {
|
||||
final DateTime startTime;
|
||||
final DateTime endTime;
|
||||
final String state;
|
||||
final int id;
|
||||
final int colorId;
|
||||
|
||||
SimpleEntityStateHistoryMoment(this.state, this.startTime, this.endTime, this.id, this.colorId);
|
||||
}*/
|
Reference in New Issue
Block a user