nymea-app/libnymea-app/models/xyseriesadapter.cpp

254 lines
7.0 KiB
C++

#include "xyseriesadapter.h"
#include <QDebug>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcLogEngine)
XYSeriesAdapter::XYSeriesAdapter(QObject *parent) : QObject(parent)
{
}
LogsModel *XYSeriesAdapter::logsModel() const
{
return m_model;
}
void XYSeriesAdapter::setLogsModel(LogsModel *logsModel)
{
if (m_model != logsModel) {
m_model = logsModel;
emit logsModelChanged();
// update();
connect(logsModel, &LogsModel::logEntryAdded, this, &XYSeriesAdapter::logEntryAdded);
}
}
QtCharts::QXYSeries *XYSeriesAdapter::xySeries() const
{
return m_series;
}
void XYSeriesAdapter::setXySeries(QtCharts::QXYSeries *series)
{
if (m_series != series) {
m_series = series;
emit xySeriesChanged();
}
}
QtCharts::QXYSeries *XYSeriesAdapter::baseSeries() const
{
return m_baseSeries;
}
void XYSeriesAdapter::setBaseSeries(QtCharts::QXYSeries *series)
{
if (m_baseSeries != series) {
m_baseSeries = series;
emit baseSeriesChanged();
connect(m_baseSeries, &QtCharts::QXYSeries::pointAdded, this, [=](int index){
if (m_series->count() > index) {
qreal value = calculateSampleValue(index);
m_series->replace(index, m_series->at(index).x(), value);
if (value < m_minValue) {
m_minValue = value;
// qDebug() << "New min:" << m_minValue;
emit minValueChanged();
}
if (value > m_maxValue) {
m_maxValue = value;
// qDebug() << "New max:" << m_maxValue;
emit maxValueChanged();
}
}
});
connect(m_baseSeries, &QtCharts::QXYSeries::pointReplaced, this, [=](int index){
if (m_series->count() > index) {
qreal value = calculateSampleValue(index);
m_series->replace(index, m_series->at(index).x(), value);
if (value < m_minValue) {
m_minValue = value;
// qDebug() << "New min:" << m_minValue;
emit minValueChanged();
}
if (value > m_maxValue) {
m_maxValue = value;
// qDebug() << "New max:" << m_maxValue;
emit maxValueChanged();
}
}
});
}
}
XYSeriesAdapter::SampleRate XYSeriesAdapter::sampleRate() const
{
return m_sampleRate;
}
void XYSeriesAdapter::setSampleRate(XYSeriesAdapter::SampleRate sampleRate)
{
if (m_sampleRate != sampleRate) {
m_sampleRate = sampleRate;
emit sampleRateChanged();
}
}
bool XYSeriesAdapter::smooth() const
{
return m_smooth;
}
void XYSeriesAdapter::setSmooth(bool smooth)
{
if (m_smooth != smooth) {
m_smooth = smooth;
emit smoothChanged();
}
}
bool XYSeriesAdapter::inverted() const
{
return m_inverted;
}
void XYSeriesAdapter::setInverted(bool inverted)
{
if (m_inverted != inverted) {
m_inverted = inverted;
emit invertedChanged();
}
}
qreal XYSeriesAdapter::maxValue() const
{
return m_maxValue;
}
qreal XYSeriesAdapter::minValue() const
{
return m_minValue;
}
void XYSeriesAdapter::ensureSamples(const QDateTime &from, const QDateTime &to)
{
// qWarning() << "Ensuring samples:" << from.toString("yyyy-MM-dd hh:mm:ss") << to.toString("yyyy-MM-dd hh:mm:ss");
if (!m_series) {
return;
}
if (m_samples.isEmpty()) {
Sample *sample = new Sample();
sample->timestamp = from.addSecs(m_sampleRate);
m_newestSample = sample->timestamp;
m_oldestSample = m_newestSample;
m_samples.append(sample);
m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), 0));
}
while (to > m_newestSample) {
Sample *sample = new Sample();
sample->timestamp = m_newestSample.addSecs(m_sampleRate);
m_newestSample = sample->timestamp;
m_samples.prepend(sample);
m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), 0));
}
while (from < m_oldestSample.addSecs(m_sampleRate)) {
Sample *sample = new Sample();
sample->timestamp = m_oldestSample.addSecs(-m_sampleRate);
m_oldestSample = sample->timestamp;
m_samples.append(sample);
m_series->append(sample->timestamp.toMSecsSinceEpoch(), 0);
}
}
void XYSeriesAdapter::logEntryAdded(LogEntry *entry)
{
if (!m_series) {
return;
}
ensureSamples(entry->timestamp(), entry->timestamp());
int idx = entry->timestamp().secsTo(m_newestSample) / m_sampleRate;
if (idx > m_samples.count()) {
qCWarning(dcLogEngine) << objectName() << "Overflowing integer size for XYSeriesAdapter!";
return;
}
// qCDebug(dcLogEngine()) << objectName() << "Inserting sample at:" << idx << entry->timestamp();
Sample *sample = m_samples.at(idx);
LogEntry *oldLast = nullptr;
// In theory we'd need to insert sorted, but only the last one actually matters for subsequent samples
// For the current sample we're calculating the median anyways, so just append/prepend t be a bit faster
if (sample->entries.count() > 0 && sample->entries.last()->timestamp() < entry->timestamp()) {
oldLast = sample->entries.last();
sample->entries.append(entry);
} else {
sample->entries.prepend(entry);
}
LogEntry *newLast = sample->entries.last();
qreal value = calculateSampleValue(idx);
m_series->replace(idx, sample->timestamp.toMSecsSinceEpoch(), value);
// qWarning() << "sample value added" << idx << entry->timestamp().time().toString("hh:mm:ss") << value;
if (value < m_minValue) {
m_minValue = value;
// qDebug() << "New min:" << m_minValue;
emit minValueChanged();
}
if (value > m_maxValue) {
m_maxValue = value;
// qDebug() << "New max:" << m_maxValue;
emit maxValueChanged();
}
// check if we need to update more samples
for (int i = idx - 1; i >= 0; i--) {
Sample *nextSample = m_samples.at(i);
if (nextSample->startingPoint == oldLast) {
nextSample->startingPoint = newLast;
qreal value = calculateSampleValue(i);
// qWarning() << "Updating" << i << value;
m_series->replace(i, nextSample->timestamp.toMSecsSinceEpoch(), value);
} else {
break;
}
}
}
qreal XYSeriesAdapter::calculateSampleValue(int index)
{
Sample *sample = m_samples.at(index);
qreal value = 0;
int count = 0;
if (sample->startingPoint) {
value = sample->startingPoint->value().toDouble();
count++;
}
foreach (LogEntry *entry, sample->entries) {
value += entry->value().toDouble();
count++;
}
if (count > 1) {
value /= count;
}
if (m_baseSeries && m_baseSeries->count() > index) {
value += m_baseSeries->at(index).y();
}
if (m_inverted) {
value *= -1;
}
return value;
}