src/ChartFermenter.cpp

Fri, 10 Feb 2023 10:09:37 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Fri, 10 Feb 2023 10:09:37 +0100
changeset 490
b67fb2e5bb41
parent 434
ebf4996ab396
permissions
-rw-r--r--

Fermenter charts, only draw lines that are really used. Better auto scaling, not using the internal version. If only a air line is to be shown, make it fat. Save the path to download the image in the settings file.

/**
 * ChartFermenter.cpp is part of bmsapp.
 *
 * bmsapp is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * bmsapp is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "ChartFermenter.h"
#include "callout.h"
#include "MainWindow.h"


ChartFermenter::ChartFermenter(QString code, QString name, QWidget *parent) : QDialog(parent)
{
    QSqlQuery query;
    double timestamp;
    bool use_air = false, use_beer=false, use_chiller = false, use_room = false;
    bool use_sp_low = false, use_sp_high = false, use_heater = false, use_cooler = false;
    QAreaSeries *pwr_cool, *pwr_heat;
    QLineSeries *pv_air, *pv_beer, *pv_chiller;
    QLineSeries *pwr_cool1, *pwr_cool0, *pwr_heat1, *pwr_heat0;
    int	min = 100, max = 0;

    qDebug() << "ChartFermenter:" << code << name;

    /*
     * First see which values are really used in the logfile.
     */
    query.prepare("SELECT * FROM log_fermenter WHERE code=:code ORDER BY datetime");
    query.bindValue(":code", code);
    query.exec();
    while (query.next()) {
	if (query.value("temp_air").toDouble() > 0)
	    use_air = true;
	if (query.value("temp_beer").toDouble() > 0)
	    use_beer = true;
	if (query.value("temp_chiller").toDouble() > 0)
	    use_chiller = true;
	if (query.value("temp_room").toDouble() > 0)
	    use_room = true;
	if (query.value("sp_low").toDouble() > 0)
	    use_sp_low = true;
	if (query.value("sp_high").toDouble() > 0)
	    use_sp_high = true;
	if (query.value("heater_power").toDouble() > 0)
	    use_heater = true;
	if (query.value("cooler_power").toDouble() > 0)
	    use_cooler = true;
	if (use_air && use_beer && use_chiller && use_room && use_sp_low && use_sp_high && use_heater && use_cooler)
	    break;
    }
//    qDebug() << "use" << use_air << use_beer << use_chiller << use_room << use_sp_low << use_sp_high << use_heater << use_cooler;

    QDialog* dialog = new QDialog(parent);
    dialog->setWindowTitle(tr("BMSapp - Fermenter log ") + "\"" + name + "\"");
    dialog->setObjectName(QString::fromUtf8("ChartFermenter"));
    dialog->setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
    dialog->resize(1024, 600);

    QPushButton *saveButton = new QPushButton(tr("Save"));
    saveButton->setAutoDefault(false);
    QIcon icon1;
    icon1.addFile(QString::fromUtf8(":icons/silk/disk.png"), QSize(), QIcon::Normal, QIcon::Off);
    saveButton->setIcon(icon1);

    QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog);
    buttonBox->setObjectName(QString::fromUtf8("buttonBox"));
    buttonBox->setOrientation(Qt::Vertical);
    buttonBox->setStandardButtons(QDialogButtonBox::Ok);
    buttonBox->addButton(saveButton,QDialogButtonBox::ActionRole);

    if (use_air)
	pv_air = new QLineSeries();
    if (use_beer)
	pv_beer = new QLineSeries();
    if (use_chiller)
	pv_chiller = new QLineSeries();
    if (use_cooler) {
	pwr_cool1 = new QLineSeries();
	pwr_cool0 = new QLineSeries();
    }
    if (use_heater) {
	pwr_heat1 = new QLineSeries();	// Top side of area
	pwr_heat0 = new QLineSeries();	// Bottom side of area
    }

    query.prepare("SELECT * FROM log_fermenter WHERE code=:code ORDER BY datetime");
    query.bindValue(":code", code);
    query.exec();
    while (query.next()) {
        timestamp = query.value("datetime").toDateTime().toSecsSinceEpoch() * 1000;
	if (use_air) {
            pv_air->append(timestamp, query.value("temp_air").toDouble());
	    if (ceil(query.value("temp_air").toDouble()) > max)
		max = ceil(query.value("temp_air").toDouble());
	    if (floor(query.value("temp_air").toDouble()) < min)
		min = floor(query.value("temp_air").toDouble());
	}
	if (use_beer) {
            pv_beer->append(timestamp, query.value("temp_beer").toDouble());
	    if (ceil(query.value("temp_beer").toDouble()) > max)
                max = ceil(query.value("temp_beer").toDouble());
            if (floor(query.value("temp_beer").toDouble()) < min)
                min = floor(query.value("temp_beer").toDouble());
	}
        if (use_chiller && query.value("temp_chiller").toDouble() > 0) {
            pv_chiller->append(timestamp, query.value("temp_chiller").toDouble());
//	    if (ceil(query.value("temp_chiller").toDouble()) > max)
//                max = ceil(query.value("temp_chiller").toDouble());
//            if (floor(query.value("temp_chiller").toDouble()) < min)
//                min = floor(query.value("temp_chiller").toDouble());
	}
	if (use_cooler) {
	    pwr_cool0->append(timestamp, 0);
            pwr_cool1->append(timestamp, query.value("cooler_power").toInt());
	}
	if (use_heater) {
	    pwr_heat0->append(timestamp, 0);
            pwr_heat1->append(timestamp, query.value("heater_power").toInt());
	}
    }
//    qDebug() << "min" << min << "max" << max << "ticks" << max-min+1;

    /*
     * If the range is small, increase it.
     */
    if ((max - min) < 5) {
	min = max - 5;
    } else if ((max - min) < 10) {
	min = max - 10;
    }

    if (use_air) {
	pv_air->setName(tr("Air"));
	if (use_beer) {
	    pv_air->setColor(QColorConstants::Svg::lightgreen);
	} else {
	    /*
	     * If there is no beer line, then make this one fat.
	     */
	    QPen pen(QColorConstants::Svg::lightgreen);
	    pen.setWidth(2);
            pv_air->setPen(pen);
	}
    }
    if (use_beer) {
	pv_beer->setName(tr("Beer"));
	QPen pen(QColorConstants::Svg::navy);
	pen.setWidth(2);
	pv_beer->setPen(pen);
    }
    if (use_chiller) {
	pv_chiller->setName(tr("Chiller"));
	pv_chiller->setColor(QColorConstants::Svg::lightsalmon);
	pv_chiller->setOpacity(0.75);
    }

    if (use_cooler) {
	pwr_cool = new QAreaSeries(pwr_cool0, pwr_cool1);
	pwr_cool->setName("Cool %");
	pwr_cool->setOpacity(0.50);
	pwr_cool->setColor(QColorConstants::Blue);
    }
    if (use_heater) {
	pwr_heat = new QAreaSeries(pwr_heat0, pwr_heat1);
	pwr_heat->setName("Heat %");
	pwr_heat->setOpacity(0.50);
	pwr_heat->setColor(QColorConstants::Red);
    }

    chart = new QChart();
    chart->setTitle(QString("%1 \"%2\"").arg(code).arg(name));
    if (use_cooler)
	chart->addSeries(pwr_cool);	// Order is important, first drawn is lowest layer.
    if (use_heater)
	chart->addSeries(pwr_heat);
    if (use_chiller)
	chart->addSeries(pv_chiller);
    if (use_air)
	chart->addSeries(pv_air);
    if (use_beer)
	chart->addSeries(pv_beer);	// Top layer

    QDateTimeAxis *axisX = new QDateTimeAxis;
    axisX->setTickCount(10);
    axisX->setFormat("dd MMM");
    axisX->setTitleText(tr("Date"));
    axisX->setLabelsFont(QFont("Helvetica", 8, QFont::Normal));
    chart->addAxis(axisX, Qt::AlignBottom);
    if (use_air)
	pv_air->attachAxis(axisX);
    if (use_beer)
	pv_beer->attachAxis(axisX);
    if (use_chiller)
	pv_chiller->attachAxis(axisX);

    QValueAxis *axisY = new QValueAxis;
    axisY->setRange(min, max);
    axisY->setTickCount(max-min+1);
    axisY->setMinorTickCount(1);
    axisY->setLabelFormat("%.1f");
    axisY->setTitleText(tr("Temp °C"));
    axisY->setLabelsFont(QFont("Helvetica", 8, QFont::Normal));
    chart->addAxis(axisY, Qt::AlignLeft);
    if (use_air)
	pv_air->attachAxis(axisY);
    if (use_beer)
	pv_beer->attachAxis(axisY);
    if (use_chiller)
	pv_chiller->attachAxis(axisY);

    if (use_heater || use_cooler) {
	QValueAxis *axisYR = new QValueAxis;
	axisYR->setRange(0, 100);
	axisYR->setTickCount(11);
	axisYR->setLabelFormat("%i");
	axisYR->setTitleText(tr("Power %"));
	axisYR->setLabelsFont(QFont("Helvetica", 8, QFont::Normal));
	chart->addAxis(axisYR, Qt::AlignRight);
	if (use_cooler)
	    pwr_cool->attachAxis(axisYR);
	if (use_heater)
	    pwr_heat->attachAxis(axisYR);
    }

    if (use_air)
	connect(pv_air, &QLineSeries::hovered, this, &ChartFermenter::tooltip);
    if (use_beer)
	connect(pv_beer, &QLineSeries::hovered, this, &ChartFermenter::tooltip);
    if (use_chiller)
	connect(pv_chiller, &QLineSeries::hovered, this, &ChartFermenter::tooltip);

    chartView = new QChartView(chart);
    chartView->setRenderHint(QPainter::Antialiasing);
    dialog->setLayout(new QHBoxLayout);
    dialog->layout()->addWidget(chartView);
    dialog->layout()->addWidget(buttonBox);

    QObject::connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
    QObject::connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
    QObject::connect(saveButton, SIGNAL(clicked()), this, SLOT(savePNG()));

    dialog->setModal(true);
    dialog->exec();
}


ChartFermenter::~ChartFermenter() {}


void ChartFermenter::savePNG()
{
    QSettings settings(QSettings::IniFormat, QSettings::UserScope, "mbse", "bmsapp");
    QString dirName;

    /*
     * First check if the directory stored in the settings file exists.
     * It might be on a removable media that was last used ...
     * If so, fallback to the user's home directory.
     */
    dirName = settings.value("paths/download").toString();
    if (! QDir(dirName).exists()) {
        dirName = QDir::homePath();
    }

    QString path = QFileDialog::getSaveFileName(this, tr("Save Image"), dirName + "/fermenter.png", tr("Image (*.png)"));
    if (path.isEmpty()) {
	QMessageBox::warning(this, tr("Save File"), tr("No image file selected."));
	return;
    }

    /*
     * Update to current selected path
     */
    settings.setValue("paths/download", QFileInfo(path).absolutePath());

    QImage img((chartView->size()), QImage::Format_ARGB32);
    QPainter painter;
    painter.begin(&img);
    chartView->render(&painter);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.end();
    img.save(path);
}


void ChartFermenter::tooltip(QPointF point, bool state)
{
    QAbstractSeries *series = qobject_cast<QAbstractSeries *>(sender());

    if (t_tooltip == 0)
	t_tooltip = new Callout(chart, series);

    if (state) {
	QDateTime timeis = QDateTime::fromMSecsSinceEpoch(point.x());
	//qDebug() << "tooltip" << QString("%1 %2°C").arg( timeis.toString("dd-MM-yyyy hh:mm") ).arg(point.y(), 2, 'f', 1);

	t_tooltip->setSeries(series);
	t_tooltip->setText(QString("%1\n%2 %3°C").arg(timeis.toString("dd-MM-yyyy hh:mm")).arg(series->name()).arg(point.y(), 2, 'f', 1));
	t_tooltip->setAnchor(point);
	t_tooltip->setZValue(11);
	t_tooltip->updateGeometry();
	t_tooltip->show();
    } else {
	t_tooltip->hide();
    }
}

mercurial