File indexing completed on 2022-10-04 13:46:46

0001 /*
0002     SPDX-FileCopyrightText: 2004, 2005, 2006 Carsten Niehaus <cniehaus@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "elementdataviewer.h"
0007 
0008 #include <element.h>
0009 
0010 #include "prefs.h"
0011 
0012 // Qt-Includes
0013 #include "kalzium_debug.h"
0014 #include <QDialogButtonBox>
0015 #include <QKeyEvent>
0016 #include <QPen>
0017 #include <QPushButton>
0018 #include <QTimer>
0019 #include <QVBoxLayout>
0020 
0021 #include <KActionCollection>
0022 #include <KConfig>
0023 #include <KConfigGroup>
0024 #include <KHelpClient>
0025 #include <KPlotAxis>
0026 #include <KPlotObject>
0027 #include <KStandardAction>
0028 #include <KUnitConversion/Converter>
0029 
0030 AxisData::AxisData(AXISTYPE type)
0031     : currentDataType(-1)
0032 {
0033     m_type = type;
0034 }
0035 
0036 double AxisData::value(int element) const
0037 {
0038     if ((element < 1) || (element > dataList.count())) {
0039         return 0.0;
0040     }
0041 
0042     return dataList[element - 1];
0043 }
0044 
0045 ElementDataViewer::ElementDataViewer(QWidget *parent)
0046     : QDialog(parent)
0047     , m_yData(new AxisData(AxisData::Y))
0048     , m_xData(new AxisData(AxisData::X))
0049 {
0050     setWindowTitle(i18nc("@title:window", "Plot Data"));
0051     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close);
0052     auto mainWidget = new QWidget(this);
0053     auto mainLayout = new QVBoxLayout(this);
0054     mainLayout->addWidget(mainWidget);
0055     connect(buttonBox, &QDialogButtonBox::accepted, this, &ElementDataViewer::accept);
0056     connect(buttonBox, &QDialogButtonBox::rejected, this, &ElementDataViewer::reject);
0057     mainLayout->addWidget(buttonBox);
0058     buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
0059 
0060     KalziumDataObject *kdo = KalziumDataObject::instance();
0061 
0062     ui.setupUi(mainWidget);
0063 
0064     m_timer = new QTimer(this);
0065     m_timer->setSingleShot(true);
0066 
0067     // setup the list of names
0068     foreach (Element *e, kdo->ElementList) {
0069         names << e->dataAsString(ChemicalDataObject::name);
0070         symbols << e->dataAsString(ChemicalDataObject::symbol);
0071         elecConfig << e->dataAsString(ChemicalDataObject::electronicConfiguration);
0072         block << e->dataAsString(ChemicalDataObject::periodTableBlock);
0073     }
0074 
0075     m_actionCollection = new KActionCollection(this);
0076     KStandardAction::quit(this, SLOT(close()), m_actionCollection);
0077 
0078     connect(m_timer, &QTimer::timeout, this, &ElementDataViewer::drawPlot);
0079     connect(ui.KCB_y, QOverload<int>::of(&KComboBox::activated), this, &ElementDataViewer::rangeChanged);
0080     connect(ui.KCB_x, QOverload<int>::of(&KComboBox::activated), this, &ElementDataViewer::rangeChanged);
0081     connect(ui.comboElementLabels, QOverload<int>::of(&KComboBox::activated), this, &ElementDataViewer::rangeChanged);
0082     connect(ui.comboElementType, QOverload<int>::of(&KComboBox::activated), this, &ElementDataViewer::rangeChanged);
0083     connect(ui.from, QOverload<int>::of(&QSpinBox::valueChanged), this, &ElementDataViewer::rangeChanged);
0084     connect(ui.to, QOverload<int>::of(&QSpinBox::valueChanged), this, &ElementDataViewer::rangeChanged);
0085     connect(buttonBox->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &ElementDataViewer::slotHelp);
0086     connect(ui.full, &QPushButton::clicked, this, &ElementDataViewer::fullRange);
0087     connect(ui.swapXYAxis, &QPushButton::clicked, this, &ElementDataViewer::swapXYAxis);
0088     drawPlot();
0089 
0090     resize(650, 500);
0091 }
0092 
0093 ElementDataViewer::~ElementDataViewer()
0094 {
0095     delete m_yData;
0096     delete m_xData;
0097 }
0098 
0099 void ElementDataViewer::slotHelp()
0100 {
0101     KHelpClient::invokeHelp(QStringLiteral("tools.html#plot_data"), QStringLiteral("kalzium"));
0102 }
0103 
0104 void ElementDataViewer::rangeChanged()
0105 {
0106     if (ui.from->value() > ui.to->value()) {
0107         ui.to->setValue(ui.from->value());
0108     }
0109 
0110     m_timer->stop();
0111     m_timer->start(500);
0112 }
0113 
0114 void ElementDataViewer::fullRange()
0115 {
0116     ui.from->setValue(1);
0117     ui.to->setValue(116);
0118 }
0119 
0120 void ElementDataViewer::swapXYAxis()
0121 {
0122     int x = ui.KCB_x->currentIndex();
0123     int y = ui.KCB_y->currentIndex();
0124 
0125     ui.KCB_x->setCurrentIndex(y);
0126     ui.KCB_y->setCurrentIndex(x);
0127 
0128     rangeChanged();
0129 }
0130 
0131 void ElementDataViewer::setLimits()
0132 {
0133     qCDebug(KALZIUM_LOG) << "ElementDataViewer::setLimits()";
0134 
0135     double x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0;
0136 
0137     getMinMax(x1, x2, m_xData);
0138     getMinMax(y1, y2, m_yData);
0139 
0140     qCDebug(KALZIUM_LOG) << x1 << " :: " << x2 << " ----- " << y1 << " :: " << y2;
0141 
0142     // JH: add some padding to show all points
0143     double dx = 0.05 * (x2 - x1);
0144     double dy = 0.05 * (y2 - y1);
0145     x1 -= dx;
0146     x2 += dx;
0147     y1 -= dy;
0148     y2 += dy;
0149 
0150     // X     // try to put a small padding to make the points on the axis visible
0151     // X     double dx = (to - from + 1) / 25 + 1.0;
0152     // X     double dy = (maxY - minY) / 10.0;
0153     // X     // in case that dy is quite small (for example, when plotting a single
0154     // X     // point)
0155     // X     if (dy < 1e-7)
0156     // X         dy = maxY / 10.0;
0157     // X     ui.plotwidget->setLimits(from - dx, to + dx, minY - dy, maxY + dy);
0158 
0159     ui.plotwidget->setLimits(x1, x2, y1, y2);
0160 }
0161 
0162 void ElementDataViewer::getMinMax(double &min, double &max, AxisData *data)
0163 {
0164     int firstElement = ui.from->value();
0165     int lastElement = ui.to->value();
0166 
0167     double minValue = data->value(firstElement);
0168     double maxValue = data->value(firstElement);
0169 
0170     qCDebug(KALZIUM_LOG) << "Taking elements from " << firstElement << " to " << lastElement;
0171 
0172     for (int _currentVal = firstElement; _currentVal <= lastElement; ++_currentVal) { // go over all selected elements
0173         double v = data->value(_currentVal);
0174 
0175         if (minValue > v) {
0176             minValue = v;
0177         }
0178         if (maxValue < v) {
0179             maxValue = v;
0180         }
0181     }
0182 
0183     qCDebug(KALZIUM_LOG) << "The value are ]" << minValue << " , " << maxValue << "[.";
0184 
0185     min = minValue;
0186     max = maxValue;
0187 }
0188 
0189 void ElementDataViewer::keyPressEvent(QKeyEvent *e)
0190 {
0191     switch (e->key()) {
0192     case Qt::Key_Plus:
0193     case Qt::Key_Equal:
0194         slotZoomIn();
0195         break;
0196     case Qt::Key_Minus:
0197     case Qt::Key_Underscore:
0198         slotZoomOut();
0199         break;
0200     case Qt::Key_Escape:
0201         close();
0202         break;
0203     }
0204 }
0205 
0206 void ElementDataViewer::slotZoomIn()
0207 {
0208 }
0209 void ElementDataViewer::slotZoomOut()
0210 {
0211 }
0212 
0213 void ElementDataViewer::setupAxisData(AxisData *data)
0214 {
0215     int selectedData = 0;
0216     if (data->type() == AxisData::X) {
0217         selectedData = ui.KCB_x->currentIndex();
0218     } else {
0219         selectedData = ui.KCB_y->currentIndex();
0220     }
0221 
0222     data->currentDataType = selectedData;
0223 
0224     // init to _something_
0225     ChemicalDataObject::BlueObelisk kind = ChemicalDataObject::mass;
0226     QString caption;
0227     int unit = KUnitConversion::NoUnit;
0228 
0229     switch (selectedData) {
0230     case AxisData::NUMBER: {
0231         kind = ChemicalDataObject::atomicNumber;
0232         caption = i18n("Atomic Number");
0233         break;
0234     }
0235     case AxisData::MASS: {
0236         kind = ChemicalDataObject::mass;
0237         caption = i18n("Atomic Mass");
0238         break;
0239     }
0240     case AxisData::EN: {
0241         kind = ChemicalDataObject::electronegativityPauling;
0242         caption = i18n("Electronegativity");
0243         break;
0244     }
0245     case AxisData::MELTINGPOINT: {
0246         kind = ChemicalDataObject::meltingpoint;
0247         caption = i18n("Melting Point");
0248         unit = Prefs::temperatureUnit();
0249         break;
0250     }
0251     case AxisData::BOILINGPOINT: {
0252         kind = ChemicalDataObject::boilingpoint;
0253         caption = i18n("Boiling Point");
0254         unit = Prefs::temperatureUnit();
0255         break;
0256     }
0257     case AxisData::ATOMICRADIUS: {
0258         kind = ChemicalDataObject::radiusVDW;
0259         caption = i18n("Atomic Radius");
0260         unit = Prefs::lengthUnit();
0261         break;
0262     }
0263     case AxisData::COVALENTRADIUS: {
0264         kind = ChemicalDataObject::radiusCovalent;
0265         caption = i18n("Covalent Radius");
0266         unit = Prefs::lengthUnit();
0267         break;
0268     }
0269     }
0270     KalziumDataObject *kdo = KalziumDataObject::instance();
0271 
0272     DoubleList dblDataList;
0273     foreach (Element *element, kdo->ElementList) {
0274         dblDataList << element->dataAsVariant(kind, unit).toDouble();
0275     }
0276 
0277     data->dataList.clear();
0278     data->dataList << dblDataList;
0279     data->kind = kind;
0280 
0281     if (unit != KUnitConversion::NoUnit) {
0282         QString stringUnit = KalziumDataObject::instance()->unitAsString(unit);
0283         data->unit = QString(' ' + stringUnit);
0284 
0285         caption.append(" [");
0286         caption.append(stringUnit);
0287         caption.append(']');
0288     }
0289 
0290     if (data->type() == AxisData::X) {
0291         ui.plotwidget->axis(KPlotWidget::BottomAxis)->setLabel(caption);
0292     } else {
0293         ui.plotwidget->axis(KPlotWidget::LeftAxis)->setLabel(caption);
0294         ui.plotwidget->axis(KPlotWidget::RightAxis)->setLabel(caption);
0295     }
0296 }
0297 
0298 void ElementDataViewer::drawPlot()
0299 {
0300     /*
0301      * to be 100% safe delete the old list
0302      */
0303     ui.plotwidget->removeAllPlotObjects();
0304 
0305     /*
0306      * spare the next step in case everything is already set and done
0307      */
0308     if (m_yData->currentDataType != ui.KCB_y->currentIndex()) {
0309         initData();
0310     }
0311 
0312     if (m_xData->currentDataType != ui.KCB_x->currentIndex()) {
0313         initData();
0314     }
0315 
0316     /*
0317      * if the user selected the elements 20 to 30 the list-values are 19 to 29!!!
0318      */
0319     const int tmpfrom = ui.from->value();
0320     const int tmpto = ui.to->value();
0321     const int from = qMin(tmpfrom, tmpto);
0322     const int to = qMax(tmpfrom, tmpto);
0323 
0324     /*
0325      * The number of elements. #20 to 30 are 30-20+1=11 Elements
0326      */
0327     int num = to - from + 1;
0328 
0329     setLimits();
0330 
0331     QSet<int> metals, metalloids, nonMetals;
0332 
0333     metals << 3 << 4 << 11 << 12 << 13;
0334     for (int i = 19; i <= 31; ++i) {
0335         metals << i;
0336     }
0337     for (int i = 37; i <= 50; ++i) {
0338         metals << i;
0339     }
0340     for (int i = 55; i <= 83; ++i) {
0341         metals << i;
0342     }
0343     for (int i = 87; i <= 116; ++i) {
0344         metals << i;
0345     }
0346 
0347     metalloids << 5 << 14 << 32 << 33 << 51 << 52 << 84 << 85;
0348 
0349     nonMetals << 1 << 2 << 6 << 7 << 8 << 9 << 10 << 15 << 16;
0350     nonMetals << 17 << 18 << 34 << 35 << 36 << 53 << 54 << 86;
0351 
0352     /*
0353      * check if the users wants to see the elementnames or not
0354      */
0355     int whatShow = ui.comboElementLabels->currentIndex();
0356 
0357     /*
0358      * Checks what type of element, the user wants to plot.
0359      * example, metal, non-metal.
0360      */
0361     int whichType = ui.comboElementType->currentIndex();
0362 
0363     KPlotObject *dataPointGreen = nullptr;
0364     KPlotObject *dataPointRed = nullptr;
0365 
0366     double av_x = 0.0;
0367     double max_x = m_xData->value(from);
0368     double min_x = m_xData->value(from);
0369     double av_y = 0.0;
0370     double max_y = m_yData->value(from);
0371     double min_y = m_yData->value(from);
0372 
0373     /*
0374      * iterate for example from element 20 to 30 and construct
0375      * the KPlotObjects
0376      */
0377     dataPointGreen = new KPlotObject(Qt::green, KPlotObject::Points, 4, KPlotObject::Star);
0378     dataPointGreen->setLabelPen(QPen(Qt::blue));
0379 
0380     dataPointRed = new KPlotObject(Qt::red, KPlotObject::Points, 4,
0381                                    KPlotObject::Star); // Star can be replaced with a cross
0382     dataPointRed->setLabelPen(QPen(Qt::blue));
0383 
0384     for (int i = from; i < to + 1; ++i) {
0385         double value_y = m_yData->value(i);
0386         double value_x = m_xData->value(i);
0387 
0388         bool known = ((value_y) > 0.0) ? true : false;
0389         // The element is know if its value is not zero
0390         bool belongs = true;
0391         // The value of belongs is one if it belongs to the particular group
0392 
0393         // See if the particular element belongs to the selected set or not.
0394         // If a particular group of elements is selected,
0395         if (whichType > 0) {
0396             belongs = false;
0397             switch (whichType) {
0398             case 1: // Plot only metals
0399                 belongs = metals.contains(i);
0400                 break;
0401             case 2: // plot only nonmetals and metalloids
0402                 belongs = (nonMetals.contains(i) || metalloids.contains(i));
0403                 break;
0404             case 3: // Plot s block elements
0405                 belongs = (block[i - 1] == QLatin1String("s"));
0406                 break;
0407             case 4: // Plot p block elements
0408                 belongs = (block[i - 1] == QLatin1String("p"));
0409                 break;
0410             case 5: // Plot d block elements
0411                 belongs = (block[i - 1] == QLatin1String("d"));
0412                 break;
0413             case 6: // plot f block elements
0414                 belongs = (block[i - 1] == QLatin1String("f"));
0415                 break;
0416             case 7: // Noble gases
0417                 belongs = ((elecConfig[i - 1]).endsWith(QLatin1String("p6")));
0418                 belongs |= (i == 2); // Include Helium
0419                 break;
0420             case 8: // Alkalie metals
0421                 belongs = ((elecConfig[i - 1]).endsWith(QLatin1String("s1")));
0422                 belongs &= (block[i - 1] == QLatin1String("s")); // exclude chromium
0423                 belongs &= (i != 1); // exclude Hydrogen
0424                 break;
0425             case 9: // Alkaline earth metals
0426                 belongs = ((elecConfig[i - 1]).endsWith(QLatin1String("s2")));
0427                 belongs &= (block[i - 1] == QLatin1String("s")); // exclude chromium
0428                 belongs &= (i != 2); // exclude Helium
0429                 break;
0430             case 10: // Lanthanides
0431                 // If element i is an f block element, with
0432                 // electronic configuration containing "f4" in it
0433                 // or the element is Lanthanum
0434                 belongs = ((block[i - 1] == QLatin1String("f")) && ((elecConfig[i - 1]).contains(QLatin1String("4f")))) || (i == 57); // Lanthanum 57
0435                 break;
0436             case 11: // Actinides
0437                 // If element i is an f block element, with
0438                 //  electronic configuration containing "f5" in it
0439                 //  or the element is Actinium
0440                 belongs = (((block[i - 1] == QLatin1String("f"))) && ((elecConfig[i - 1]).contains(QLatin1String("5f")))) || (i == 89); // Actinium 89
0441                 break;
0442             case 12: // Radio active
0443                 belongs = ((i == 43) || (i == 61) || (i > 84));
0444                 // Technitium prothomium and then polonium onwards.
0445                 break;
0446             default:
0447                 whichType = 0;
0448                 belongs = true;
0449             }
0450         }
0451         if (belongs) {
0452             if (known) {
0453                 av_x += value_x;
0454                 av_y += value_y;
0455 
0456                 if (value_x > max_x) {
0457                     max_x = value_x;
0458                 }
0459                 if (value_y > max_y) {
0460                     max_y = value_y;
0461                 }
0462                 if (value_x < min_x) {
0463                     min_x = value_x;
0464                 }
0465                 if (value_y < min_y) {
0466                     min_y = value_y;
0467                 }
0468 
0469                 QString lbl;
0470                 if (whatShow > 0) { // The users wants to see the labels
0471                     lbl = whatShow == 1 ? names[i - 1] : symbols[i - 1];
0472                 }
0473 
0474                 dataPointGreen->addPoint(value_x, value_y, lbl);
0475             } else { // unknown value
0476                 // num is required while finding the average, if an element is not
0477                 // known it should not contribute to the average.
0478                 --num;
0479 
0480                 QString lbl;
0481                 if (whatShow > 0) { // The user wants to see the labels
0482                     lbl = whatShow == 1 ? names[i - 1] : symbols[i - 1];
0483                 }
0484 
0485                 dataPointRed->addPoint(value_x, value_y, lbl);
0486                 // For an Unknown value, use a red point to mark the data-point.
0487             }
0488         } else { // The element does not belong to the set
0489             // num is required while finding average, if an element is
0490             // not in the selected set, it should not contribute to the avg.
0491             --num;
0492         }
0493     }
0494 
0495     ui.plotwidget->addPlotObject(dataPointGreen);
0496     ui.plotwidget->addPlotObject(dataPointRed);
0497 
0498     if (num > 0) {
0499         // now set the values for the min, max and average value
0500         ui.av_x->setText(QString::number(av_x / num).append(m_xData->unit));
0501         ui.minimum_x->setText(QString::number(min_x).append(m_xData->unit));
0502         ui.maximum_x->setText(QString::number(max_x).append(m_xData->unit));
0503 
0504         ui.av_y->setText(QString::number(av_y / num).append(m_yData->unit));
0505         ui.minimum_y->setText(QString::number(min_y).append(m_yData->unit));
0506         ui.maximum_y->setText(QString::number(max_y).append(m_yData->unit));
0507     } else {
0508         ui.av_x->setText(QString::number(0.0));
0509         ui.minimum_x->setText(QString::number(0.0));
0510         ui.maximum_x->setText(QString::number(0.0));
0511         ui.av_y->setText(QString::number(0.0));
0512         ui.minimum_y->setText(QString::number(0.0));
0513         ui.maximum_y->setText(QString::number(0.0));
0514     }
0515 }
0516 
0517 void ElementDataViewer::initData()
0518 {
0519     setupAxisData(m_xData);
0520     setupAxisData(m_yData);
0521 }