File indexing completed on 2025-01-26 03:34:12

0001 /*
0002     File                 : Histogram.cpp
0003     Project              : LabPlot
0004     Description          : Histogram
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2016 Anu Mittal <anu22mittal@gmail.com>
0007     SPDX-FileCopyrightText: 2016-2024 Alexander Semke <alexander.semke@web.de>
0008     SPDX-FileCopyrightText: 2017-2018 Garvit Khatri <garvitdelhi@gmail.com>
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 /*!
0013   \class Histogram
0014   \brief A 2D-curve, provides an interface for editing many properties of the curve.
0015 
0016   \ingroup worksheet
0017   */
0018 #include "Histogram.h"
0019 #include "HistogramPrivate.h"
0020 #include "backend/core/AbstractColumn.h"
0021 #include "backend/core/Folder.h"
0022 #include "backend/core/Settings.h"
0023 #include "backend/core/column/Column.h"
0024 #include "backend/lib/XmlStreamReader.h"
0025 #include "backend/lib/commandtemplates.h"
0026 #include "backend/lib/macrosCurve.h"
0027 #include "backend/lib/trace.h"
0028 #include "backend/spreadsheet/Spreadsheet.h"
0029 #include "backend/worksheet/Background.h"
0030 #include "backend/worksheet/Line.h"
0031 #include "backend/worksheet/Worksheet.h"
0032 #include "backend/worksheet/plots/cartesian/ErrorBar.h"
0033 #include "backend/worksheet/plots/cartesian/ErrorBarStyle.h"
0034 #include "backend/worksheet/plots/cartesian/Symbol.h"
0035 #include "backend/worksheet/plots/cartesian/Value.h"
0036 #include "tools/ImageTools.h"
0037 
0038 #include <QMenu>
0039 #include <QPainter>
0040 
0041 #include <KConfig>
0042 #include <KConfigGroup>
0043 #include <KLocalizedString>
0044 #include <QGraphicsSceneMouseEvent>
0045 
0046 CURVE_COLUMN_CONNECT(Histogram, Data, data, recalc)
0047 
0048 Histogram::Histogram(const QString& name)
0049     : Plot(name, new HistogramPrivate(this), AspectType::Histogram) {
0050     init();
0051 }
0052 
0053 Histogram::Histogram(const QString& name, HistogramPrivate* dd)
0054     : Plot(name, dd, AspectType::Histogram) {
0055     init();
0056 }
0057 
0058 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0059 // and is deleted during the cleanup in QGraphicsScene
0060 Histogram::~Histogram() = default;
0061 
0062 void Histogram::init() {
0063     Q_D(Histogram);
0064 
0065     KConfig config;
0066     KConfigGroup group = config.group(QStringLiteral("Histogram"));
0067 
0068     d->type = (Histogram::Type)group.readEntry(QStringLiteral("Type"), (int)Histogram::Ordinary);
0069     d->orientation = (Histogram::Orientation)group.readEntry(QStringLiteral("Orientation"), (int)Histogram::Vertical);
0070     d->normalization = (Histogram::Normalization)group.readEntry(QStringLiteral("Normalization"), (int)Histogram::Count);
0071     d->binningMethod = (Histogram::BinningMethod)group.readEntry(QStringLiteral("BinningMethod"), (int)Histogram::SquareRoot);
0072     d->binCount = group.readEntry(QStringLiteral("BinCount"), 10);
0073     d->binWidth = group.readEntry(QStringLiteral("BinWidth"), 1.0);
0074     d->autoBinRanges = group.readEntry(QStringLiteral("AutoBinRanges"), true);
0075     d->binRangesMin = 0.0;
0076     d->binRangesMax = 1.0;
0077 
0078     // line
0079     d->line = new Line(QString());
0080     d->line->setHistogramLineTypeAvailable(true);
0081     d->line->setHidden(true);
0082     addChild(d->line);
0083     d->line->init(group);
0084     connect(d->line, &Line::histogramLineTypeChanged, [=] {
0085         d->updateLines();
0086     });
0087     connect(d->line, &Line::updatePixmapRequested, [=] {
0088         d->updatePixmap();
0089     });
0090     connect(d->line, &Line::updateRequested, [=] {
0091         d->recalcShapeAndBoundingRect();
0092     });
0093 
0094     // symbol
0095     d->symbol = new Symbol(QString());
0096     addChild(d->symbol);
0097     d->symbol->setHidden(true);
0098     d->symbol->init(group);
0099     connect(d->symbol, &Symbol::updateRequested, [=] {
0100         d->updateSymbols();
0101     });
0102     connect(d->symbol, &Symbol::updatePixmapRequested, [=] {
0103         d->updatePixmap();
0104     });
0105 
0106     // values
0107     d->value = new Value(QString());
0108     addChild(d->value);
0109     d->value->setHidden(true);
0110     d->value->setcenterPositionAvailable(true);
0111     d->value->init(group);
0112     connect(d->value, &Value::updatePixmapRequested, [=] {
0113         d->updatePixmap();
0114     });
0115     connect(d->value, &Value::updateRequested, [=] {
0116         d->updateValues();
0117     });
0118 
0119     // Background/Filling
0120     d->background = new Background(QString());
0121     d->background->setPrefix(QStringLiteral("Filling"));
0122     d->background->setEnabledAvailable(true);
0123     addChild(d->background);
0124     d->background->setHidden(true);
0125     d->background->init(group);
0126     connect(d->background, &Background::updateRequested, [=] {
0127         d->updatePixmap();
0128     });
0129     connect(d->background, &Background::updatePositionRequested, [=] {
0130         d->updateFilling();
0131     });
0132 
0133     // error bars
0134     d->errorBar = new ErrorBar(QString());
0135     addChild(d->errorBar);
0136     d->errorBar->setHidden(true);
0137     d->errorBar->init(group);
0138     connect(d->errorBar, &ErrorBar::updateRequested, [=] {
0139         d->updateErrorBars();
0140     });
0141 
0142     d->errorBarStyle = new ErrorBarStyle(QString());
0143     addChild(d->errorBarStyle);
0144     d->errorBarStyle->setHidden(true);
0145     d->errorBarStyle->init(group);
0146     connect(d->errorBarStyle, &ErrorBarStyle::updateRequested, [=] {
0147         d->updateErrorBars();
0148     });
0149     connect(d->errorBarStyle, &ErrorBarStyle::updatePixmapRequested, [=] {
0150         d->updatePixmap();
0151     });
0152 
0153     // marginal plots (rug, histogram, boxplot)
0154     d->rugEnabled = group.readEntry(QStringLiteral("RugEnabled"), false);
0155     d->rugLength = group.readEntry(QStringLiteral("RugLength"), Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point));
0156     d->rugWidth = group.readEntry(QStringLiteral("RugWidth"), 0.0);
0157     d->rugOffset = group.readEntry(QStringLiteral("RugOffset"), 0.0);
0158     this->initActions();
0159 }
0160 
0161 void Histogram::initActions() {
0162 }
0163 
0164 /*!
0165  * creates a new spreadsheet having the data with the positions and the values of the bins.
0166  * the new spreadsheet is added to the current folder.
0167  */
0168 void Histogram::createDataSpreadsheet() {
0169     if (!bins() || !binValues())
0170         return;
0171 
0172     auto* spreadsheet = new Spreadsheet(i18n("%1 - Data", name()));
0173     spreadsheet->removeColumns(0, spreadsheet->columnCount()); // remove default columns
0174     spreadsheet->setRowCount(bins()->rowCount());
0175 
0176     // bin positions
0177     auto* data = static_cast<const Column*>(bins())->data();
0178     auto* xColumn = new Column(i18n("bin positions"), *static_cast<QVector<double>*>(data));
0179     xColumn->setPlotDesignation(AbstractColumn::PlotDesignation::X);
0180     spreadsheet->addChild(xColumn);
0181 
0182     // y values
0183     data = static_cast<const Column*>(binValues())->data();
0184     auto* yColumn = new Column(i18n("bin values"), *static_cast<QVector<double>*>(data));
0185     yColumn->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
0186     spreadsheet->addChild(yColumn);
0187 
0188     // add the new spreadsheet to the current folder
0189     folder()->addChild(spreadsheet);
0190 }
0191 
0192 QMenu* Histogram::createContextMenu() {
0193     QMenu* menu = WorksheetElement::createContextMenu();
0194     QAction* visibilityAction = this->visibilityAction();
0195 
0196     //"data analysis" menu
0197     auto* analysisMenu = new QMenu(i18n("Analysis"));
0198 
0199     // TODO: if there are more actions, add a group for all fit types
0200     auto* fitGaussianAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Fit Gaussian (Normal) Distribution"));
0201     analysisMenu->addAction(fitGaussianAction);
0202     connect(fitGaussianAction, &QAction::triggered, this, [=]() {
0203         m_plot->addHistogramFit(this, nsl_sf_stats_gaussian);
0204     });
0205 
0206     auto* fitExponentialAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Fit Exponential Distribution"));
0207     analysisMenu->addAction(fitExponentialAction);
0208     connect(fitExponentialAction, &QAction::triggered, this, [=]() {
0209         m_plot->addHistogramFit(this, nsl_sf_stats_exponential);
0210     });
0211 
0212     auto* fitLaplaceAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Fit Laplace Distribution"));
0213     analysisMenu->addAction(fitLaplaceAction);
0214     connect(fitLaplaceAction, &QAction::triggered, this, [=]() {
0215         m_plot->addHistogramFit(this, nsl_sf_stats_laplace);
0216     });
0217 
0218     auto* fitCauchyAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Fit Cauchy-Lorentz Distribution"));
0219     analysisMenu->addAction(fitCauchyAction);
0220     connect(fitCauchyAction, &QAction::triggered, this, [=]() {
0221         m_plot->addHistogramFit(this, nsl_sf_stats_cauchy_lorentz);
0222     });
0223 
0224     auto* fitLognormalAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Fit Log-normal Distribution"));
0225     analysisMenu->addAction(fitLognormalAction);
0226     connect(fitLognormalAction, &QAction::triggered, this, [=]() {
0227         m_plot->addHistogramFit(this, nsl_sf_stats_lognormal);
0228     });
0229 
0230     auto* fitPoissonAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Fit Poisson Distribution"));
0231     analysisMenu->addAction(fitPoissonAction);
0232     connect(fitPoissonAction, &QAction::triggered, this, [=]() {
0233         m_plot->addHistogramFit(this, nsl_sf_stats_poisson);
0234     });
0235 
0236     auto* fitBinomialAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-fit-curve")), i18n("Fit Binomial Distribution"));
0237     analysisMenu->addAction(fitBinomialAction);
0238     connect(fitBinomialAction, &QAction::triggered, this, [=]() {
0239         m_plot->addHistogramFit(this, nsl_sf_stats_binomial);
0240     });
0241 
0242     menu->insertMenu(visibilityAction, analysisMenu);
0243     menu->insertSeparator(visibilityAction);
0244 
0245     return menu;
0246 }
0247 
0248 /*!
0249   Returns an icon to be used in the project explorer.
0250   */
0251 QIcon Histogram::icon() const {
0252     return QIcon::fromTheme(QStringLiteral("view-object-histogram-linear"));
0253 }
0254 
0255 // ##############################################################################
0256 // ##########################  getter methods  ##################################
0257 // ##############################################################################
0258 //  general
0259 BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::Type, type, type)
0260 BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::Orientation, orientation, orientation)
0261 BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::Normalization, normalization, normalization)
0262 BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::BinningMethod, binningMethod, binningMethod)
0263 BASIC_SHARED_D_READER_IMPL(Histogram, int, binCount, binCount)
0264 BASIC_SHARED_D_READER_IMPL(Histogram, double, binWidth, binWidth)
0265 BASIC_SHARED_D_READER_IMPL(Histogram, bool, autoBinRanges, autoBinRanges)
0266 BASIC_SHARED_D_READER_IMPL(Histogram, double, binRangesMin, binRangesMin)
0267 BASIC_SHARED_D_READER_IMPL(Histogram, double, binRangesMax, binRangesMax)
0268 BASIC_SHARED_D_READER_IMPL(Histogram, const AbstractColumn*, dataColumn, dataColumn)
0269 BASIC_SHARED_D_READER_IMPL(Histogram, QString, dataColumnPath, dataColumnPath)
0270 
0271 // line
0272 Line* Histogram::line() const {
0273     Q_D(const Histogram);
0274     return d->line;
0275 }
0276 
0277 // symbols
0278 Symbol* Histogram::symbol() const {
0279     Q_D(const Histogram);
0280     return d->symbol;
0281 }
0282 
0283 // values
0284 Value* Histogram::value() const {
0285     Q_D(const Histogram);
0286     return d->value;
0287 }
0288 
0289 // filling
0290 Background* Histogram::background() const {
0291     Q_D(const Histogram);
0292     return d->background;
0293 }
0294 
0295 // error bars
0296 ErrorBar* Histogram::errorBar() const {
0297     Q_D(const Histogram);
0298     return d->errorBar;
0299 }
0300 
0301 ErrorBarStyle* Histogram::errorBarStyle() const {
0302     Q_D(const Histogram);
0303     return d->errorBarStyle;
0304 }
0305 
0306 // margin plots
0307 BASIC_SHARED_D_READER_IMPL(Histogram, bool, rugEnabled, rugEnabled)
0308 BASIC_SHARED_D_READER_IMPL(Histogram, double, rugLength, rugLength)
0309 BASIC_SHARED_D_READER_IMPL(Histogram, double, rugWidth, rugWidth)
0310 BASIC_SHARED_D_READER_IMPL(Histogram, double, rugOffset, rugOffset)
0311 
0312 double Histogram::minimum(const Dimension dim) const {
0313     Q_D(const Histogram);
0314     switch (dim) {
0315     case Dimension::X:
0316         return d->xMinimum();
0317     case Dimension::Y:
0318         return d->yMinimum();
0319     }
0320     return NAN;
0321 }
0322 
0323 double Histogram::maximum(const Dimension dim) const {
0324     Q_D(const Histogram);
0325     switch (dim) {
0326     case Dimension::X:
0327         return d->xMaximum();
0328     case Dimension::Y:
0329         return d->yMaximum();
0330     }
0331     return NAN;
0332 }
0333 
0334 bool Histogram::hasData() const {
0335     Q_D(const Histogram);
0336     return (d->dataColumn != nullptr);
0337 }
0338 
0339 bool Histogram::usingColumn(const Column* column) const {
0340     Q_D(const Histogram);
0341     return (d->dataColumn == column || (d->errorBar->type() == ErrorBar::Type::Symmetric && d->errorBar->plusColumn() == column)
0342             || (d->errorBar->type() == ErrorBar::Type::Asymmetric && (d->errorBar->plusColumn() == column || d->errorBar->minusColumn() == column)));
0343 }
0344 
0345 void Histogram::updateColumnDependencies(const AbstractColumn* column) {
0346     Q_D(Histogram);
0347     setUndoAware(false);
0348     const QString& columnPath = column->path();
0349 
0350     if (d->dataColumn == column) // the column is the same and was just renamed -> update the column path
0351         d->dataColumnPath = columnPath;
0352     else if (d->dataColumnPath == columnPath) // another column was renamed to the current path -> set and connect to the new column
0353         setDataColumn(column);
0354 
0355     if (d->value->column() == column)
0356         d->value->setColumnPath(columnPath);
0357     else if (d->value->columnPath() == columnPath)
0358         d->value->setColumn(column);
0359 
0360     if (d->errorBar->plusColumn() == column)
0361         d->errorBar->setPlusColumnPath(columnPath);
0362     else if (d->errorBar->plusColumnPath() == columnPath)
0363         d->errorBar->setPlusColumn(column);
0364 
0365     if (d->errorBar->minusColumn() == column)
0366         d->errorBar->setMinusColumnPath(columnPath);
0367     else if (d->errorBar->minusColumnPath() == columnPath)
0368         d->errorBar->setMinusColumn(column);
0369 
0370     setUndoAware(true);
0371 }
0372 
0373 QColor Histogram::color() const {
0374     Q_D(const Histogram);
0375     if (d->background->enabled())
0376         return d->background->firstColor();
0377     else if (d->line->style() != Qt::PenStyle::NoPen)
0378         return d->line->pen().color();
0379     return QColor();
0380 }
0381 
0382 const AbstractColumn* Histogram::bins() const {
0383     D(Histogram);
0384     return d->bins();
0385 }
0386 
0387 const AbstractColumn* Histogram::binValues() const {
0388     D(Histogram);
0389     return d->binValues();
0390 }
0391 
0392 const AbstractColumn* Histogram::binPDValues() const {
0393     D(Histogram);
0394     return d->binPDValues();
0395 }
0396 
0397 // ##############################################################################
0398 // #################  setter methods and undo commands ##########################
0399 // ##############################################################################
0400 
0401 // General
0402 CURVE_COLUMN_SETTER_CMD_IMPL_F_S(Histogram, Data, data, recalc)
0403 void Histogram::setDataColumn(const AbstractColumn* column) {
0404     Q_D(Histogram);
0405     if (column != d->dataColumn)
0406         exec(new HistogramSetDataColumnCmd(d, column, ki18n("%1: set data column")));
0407 }
0408 
0409 void Histogram::setDataColumnPath(const QString& path) {
0410     Q_D(Histogram);
0411     d->dataColumnPath = path;
0412 }
0413 
0414 STD_SETTER_CMD_IMPL_F_S(Histogram, SetType, Histogram::Type, type, updateType)
0415 void Histogram::setType(Histogram::Type type) {
0416     Q_D(Histogram);
0417     if (type != d->type)
0418         exec(new HistogramSetTypeCmd(d, type, ki18n("%1: set histogram type")));
0419 }
0420 
0421 STD_SETTER_CMD_IMPL_F_S(Histogram, SetOrientation, Histogram::Orientation, orientation, updateOrientation)
0422 void Histogram::setOrientation(Histogram::Orientation orientation) {
0423     Q_D(Histogram);
0424     if (orientation != d->orientation)
0425         exec(new HistogramSetOrientationCmd(d, orientation, ki18n("%1: set histogram orientation")));
0426 }
0427 
0428 STD_SETTER_CMD_IMPL_F_S(Histogram, SetNormalization, Histogram::Normalization, normalization, updateOrientation)
0429 void Histogram::setNormalization(Histogram::Normalization normalization) {
0430     Q_D(Histogram);
0431     if (normalization != d->normalization)
0432         exec(new HistogramSetNormalizationCmd(d, normalization, ki18n("%1: set histogram normalization")));
0433 }
0434 
0435 STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinningMethod, Histogram::BinningMethod, binningMethod, recalc)
0436 void Histogram::setBinningMethod(Histogram::BinningMethod method) {
0437     Q_D(Histogram);
0438     if (method != d->binningMethod)
0439         exec(new HistogramSetBinningMethodCmd(d, method, ki18n("%1: set binning method")));
0440 }
0441 
0442 STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinCount, int, binCount, recalc)
0443 void Histogram::setBinCount(int count) {
0444     Q_D(Histogram);
0445     if (count != d->binCount)
0446         exec(new HistogramSetBinCountCmd(d, count, ki18n("%1: set bin count")));
0447 }
0448 
0449 STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinWidth, double, binWidth, recalc)
0450 void Histogram::setBinWidth(double width) {
0451     Q_D(Histogram);
0452     if (width != d->binWidth)
0453         exec(new HistogramSetBinWidthCmd(d, width, ki18n("%1: set bin width")));
0454 }
0455 
0456 class HistogramSetAutoBinRangesCmd : public QUndoCommand {
0457 public:
0458     HistogramSetAutoBinRangesCmd(HistogramPrivate* private_obj, bool autoBinRanges)
0459         : m_private(private_obj)
0460         , m_autoBinRanges(autoBinRanges) {
0461         setText(i18n("%1: change auto bin ranges", m_private->name()));
0462     }
0463 
0464     void redo() override {
0465         m_autoBinRangesOld = m_private->autoBinRanges;
0466         m_private->autoBinRanges = m_autoBinRanges;
0467         if (m_autoBinRanges) {
0468             m_binRangesMinOld = m_private->binRangesMin;
0469             m_binRangesMaxOld = m_private->binRangesMax;
0470             m_private->q->recalc();
0471         }
0472         Q_EMIT m_private->q->autoBinRangesChanged(m_autoBinRanges);
0473     }
0474 
0475     void undo() override {
0476         m_private->autoBinRanges = m_autoBinRangesOld;
0477         if (!m_autoBinRangesOld) {
0478             if (m_private->binRangesMin != m_binRangesMinOld) {
0479                 m_private->binRangesMin = m_binRangesMinOld;
0480                 Q_EMIT m_private->q->binRangesMinChanged(m_private->binRangesMin);
0481             }
0482             if (m_private->binRangesMax != m_binRangesMaxOld) {
0483                 m_private->binRangesMax = m_binRangesMaxOld;
0484                 Q_EMIT m_private->q->binRangesMaxChanged(m_private->binRangesMax);
0485             }
0486             m_private->recalc();
0487         }
0488         Q_EMIT m_private->q->autoBinRangesChanged(m_autoBinRangesOld);
0489     }
0490 
0491 private:
0492     HistogramPrivate* m_private;
0493     double m_binRangesMinOld{0.0};
0494     double m_binRangesMaxOld{0.0};
0495     bool m_autoBinRanges;
0496     bool m_autoBinRangesOld{false};
0497 };
0498 
0499 void Histogram::setAutoBinRanges(bool autoBinRanges) {
0500     Q_D(Histogram);
0501     if (autoBinRanges != d->autoBinRanges)
0502         exec(new HistogramSetAutoBinRangesCmd(d, autoBinRanges));
0503 }
0504 
0505 STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinRangesMin, double, binRangesMin, recalc)
0506 void Histogram::setBinRangesMin(double binRangesMin) {
0507     Q_D(Histogram);
0508     if (binRangesMin != d->binRangesMin)
0509         exec(new HistogramSetBinRangesMinCmd(d, binRangesMin, ki18n("%1: set bin ranges start")));
0510 }
0511 
0512 STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinRangesMax, double, binRangesMax, recalc)
0513 void Histogram::setBinRangesMax(double binRangesMax) {
0514     Q_D(Histogram);
0515     if (binRangesMax != d->binRangesMax)
0516         exec(new HistogramSetBinRangesMaxCmd(d, binRangesMax, ki18n("%1: set bin ranges end")));
0517 }
0518 
0519 // margin plots
0520 STD_SETTER_CMD_IMPL_F_S(Histogram, SetRugEnabled, bool, rugEnabled, updateRug)
0521 void Histogram::setRugEnabled(bool enabled) {
0522     Q_D(Histogram);
0523     if (enabled != d->rugEnabled)
0524         exec(new HistogramSetRugEnabledCmd(d, enabled, ki18n("%1: change rug enabled")));
0525 }
0526 
0527 STD_SETTER_CMD_IMPL_F_S(Histogram, SetRugWidth, double, rugWidth, updatePixmap)
0528 void Histogram::setRugWidth(double width) {
0529     Q_D(Histogram);
0530     if (width != d->rugWidth)
0531         exec(new HistogramSetRugWidthCmd(d, width, ki18n("%1: change rug width")));
0532 }
0533 
0534 STD_SETTER_CMD_IMPL_F_S(Histogram, SetRugLength, double, rugLength, updateRug)
0535 void Histogram::setRugLength(double length) {
0536     Q_D(Histogram);
0537     if (length != d->rugLength)
0538         exec(new HistogramSetRugLengthCmd(d, length, ki18n("%1: change rug length")));
0539 }
0540 
0541 STD_SETTER_CMD_IMPL_F_S(Histogram, SetRugOffset, double, rugOffset, updateRug)
0542 void Histogram::setRugOffset(double offset) {
0543     Q_D(Histogram);
0544     if (offset != d->rugOffset)
0545         exec(new HistogramSetRugOffsetCmd(d, offset, ki18n("%1: change rug offset")));
0546 }
0547 
0548 // ##############################################################################
0549 // #################################  SLOTS  ####################################
0550 // ##############################################################################
0551 void Histogram::retransform() {
0552     d_ptr->retransform();
0553 }
0554 
0555 void Histogram::recalc() {
0556     D(Histogram);
0557     d->recalc();
0558 }
0559 
0560 // TODO
0561 void Histogram::handleResize(double horizontalRatio, double /*verticalRatio*/, bool /*pageResize*/) {
0562     Q_D(const Histogram);
0563 
0564     // setValuesDistance(d->distance*);
0565     QFont font = d->value->font();
0566     font.setPointSizeF(font.pointSizeF() * horizontalRatio);
0567     d->value->setFont(font);
0568 
0569     retransform();
0570 }
0571 
0572 void Histogram::updateValues() {
0573     D(Histogram);
0574     d->updateValues();
0575 }
0576 
0577 void Histogram::dataColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0578     Q_D(Histogram);
0579     if (aspect == d->dataColumn) {
0580         d->dataColumn = nullptr;
0581         d->retransform();
0582     }
0583 }
0584 
0585 void Histogram::updateErrorBars() {
0586     Q_D(Histogram);
0587     d->updateErrorBars();
0588 }
0589 
0590 // ##############################################################################
0591 // ######################### Private implementation #############################
0592 // ##############################################################################
0593 HistogramPrivate::HistogramPrivate(Histogram* owner)
0594     : PlotPrivate(owner)
0595     , q(owner) {
0596     setFlag(QGraphicsItem::ItemIsSelectable, true);
0597     setAcceptHoverEvents(false);
0598 }
0599 
0600 HistogramPrivate::~HistogramPrivate() {
0601     if (m_histogram)
0602         gsl_histogram_free(m_histogram);
0603 }
0604 
0605 double HistogramPrivate::getMaximumOccuranceofHistogram() const {
0606     if (m_histogram) {
0607         double yMaxRange = -INFINITY;
0608         switch (type) {
0609         case Histogram::Ordinary: {
0610             size_t maxYAddes = gsl_histogram_max_bin(m_histogram);
0611             yMaxRange = gsl_histogram_get(m_histogram, maxYAddes);
0612             break;
0613         }
0614         case Histogram::Cumulative: {
0615             size_t maxYAddes = gsl_histogram_max_bin(m_histogram);
0616             yMaxRange = gsl_histogram_get(m_histogram, maxYAddes);
0617             double point = 0.0;
0618             for (size_t i = 0; i < m_bins; ++i) {
0619                 point += gsl_histogram_get(m_histogram, i);
0620                 if (point > yMaxRange) {
0621                     yMaxRange = point;
0622                 }
0623             }
0624             // yMaxRange = dataColumn->rowCount();
0625             break;
0626         }
0627         case Histogram::AvgShift: {
0628             // TODO
0629         }
0630         }
0631 
0632         switch (normalization) {
0633         case Histogram::Count:
0634             break;
0635         case Histogram::Probability:
0636             yMaxRange = yMaxRange / totalCount;
0637             break;
0638         case Histogram::CountDensity: {
0639             const double width = (binRangesMax - binRangesMin) / m_bins;
0640             yMaxRange = yMaxRange / width;
0641             break;
0642         }
0643         case Histogram::ProbabilityDensity: {
0644             const double width = (binRangesMax - binRangesMin) / m_bins;
0645             yMaxRange = yMaxRange / totalCount / width;
0646             break;
0647         }
0648         }
0649 
0650         return yMaxRange;
0651     }
0652 
0653     return -INFINITY;
0654 }
0655 
0656 double HistogramPrivate::xMinimum() const {
0657     switch (orientation) {
0658     case Histogram::Vertical:
0659         return autoBinRanges ? dataColumn->minimum() : binRangesMin;
0660     case Histogram::Horizontal:
0661         return 0;
0662     }
0663     return INFINITY;
0664 }
0665 
0666 double HistogramPrivate::xMaximum() const {
0667     switch (orientation) {
0668     case Histogram::Vertical:
0669         return autoBinRanges ? dataColumn->maximum() : binRangesMax;
0670     case Histogram::Horizontal:
0671         return getMaximumOccuranceofHistogram();
0672     }
0673     return -INFINITY;
0674 }
0675 
0676 double HistogramPrivate::yMinimum() const {
0677     switch (orientation) {
0678     case Histogram::Vertical:
0679         return 0;
0680     case Histogram::Horizontal:
0681         return autoBinRanges ? dataColumn->minimum() : binRangesMin;
0682     }
0683     return INFINITY;
0684 }
0685 
0686 double HistogramPrivate::yMaximum() const {
0687     switch (orientation) {
0688     case Histogram::Vertical:
0689         return getMaximumOccuranceofHistogram();
0690     case Histogram::Horizontal:
0691         return autoBinRanges ? dataColumn->maximum() : binRangesMax;
0692     }
0693     return -INFINITY;
0694 }
0695 
0696 const AbstractColumn* HistogramPrivate::bins() {
0697     if (!m_binsColumn) {
0698         m_binsColumn = new Column(QStringLiteral("bins"));
0699 
0700         const double width = (binRangesMax - binRangesMin) / m_bins;
0701         m_binsColumn->resizeTo(m_bins);
0702         for (size_t i = 0; i < m_bins; ++i) {
0703             const double x = binRangesMin + i * width;
0704             m_binsColumn->setValueAt(i, x);
0705         }
0706     }
0707 
0708     return m_binsColumn;
0709 }
0710 
0711 const AbstractColumn* HistogramPrivate::binValues() {
0712     if (!m_binValuesColumn) {
0713         m_binValuesColumn = new Column(QStringLiteral("values"));
0714 
0715         m_binValuesColumn->resizeTo(m_bins);
0716         double value = 0.;
0717         for (size_t i = 0; i < m_bins; ++i) {
0718             histogramValue(value, i);
0719             m_binValuesColumn->setValueAt(i, value);
0720         }
0721     }
0722 
0723     return m_binValuesColumn;
0724 }
0725 
0726 /*!
0727  * returns a column with the bin values in the probability density normalization
0728  * \return
0729  */
0730 const AbstractColumn* HistogramPrivate::binPDValues() {
0731     if (!m_binPDValuesColumn) {
0732         m_binPDValuesColumn = new Column(QStringLiteral("values"));
0733 
0734         m_binPDValuesColumn->resizeTo(m_bins);
0735         const double width = (binRangesMax - binRangesMin) / m_bins;
0736         for (size_t i = 0; i < m_bins; ++i)
0737             m_binPDValuesColumn->setValueAt(i, gsl_histogram_get(m_histogram, i) / totalCount / width); // probability density normalization
0738     }
0739 
0740     return m_binPDValuesColumn;
0741 }
0742 
0743 /*!
0744   called when the size of the plot or its data ranges (manual changes, zooming, etc.) were changed.
0745   recalculates the position of the scene points to be drawn.
0746   triggers the update of lines, drop lines, symbols etc.
0747 */
0748 void HistogramPrivate::retransform() {
0749     const bool suppressed = suppressRetransform || q->isLoading();
0750     Q_EMIT trackRetransformCalled(suppressed);
0751     if (suppressed)
0752         return;
0753 
0754     if (!isVisible())
0755         return;
0756 
0757     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0758 
0759     if (!dataColumn) {
0760         linePath = QPainterPath();
0761         symbolsPath = QPainterPath();
0762         valuesPath = QPainterPath();
0763         errorBarsPath = QPainterPath();
0764         rugPath = QPainterPath();
0765         m_shape = QPainterPath();
0766         lines.clear();
0767         linesUnclipped.clear();
0768         pointsLogical.clear();
0769         pointsScene.clear();
0770         visiblePoints.clear();
0771         valuesPoints.clear();
0772         valuesStrings.clear();
0773         fillPolygon.clear();
0774         recalcShapeAndBoundingRect();
0775         return;
0776     }
0777 
0778     suppressRecalc = true;
0779     updateLines();
0780     updateSymbols();
0781     updateErrorBars();
0782     updateRug();
0783     suppressRecalc = false;
0784     updateValues();
0785 }
0786 
0787 /*!
0788  * called when the data was changed. recalculates the histogram.
0789  */
0790 void HistogramPrivate::recalc() {
0791     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0792 
0793     if (m_histogram) {
0794         gsl_histogram_free(m_histogram);
0795         m_histogram = nullptr;
0796     }
0797 
0798     if (!dataColumn)
0799         return;
0800 
0801     // in case wrong bin range was specified, call retransform() to reset
0802     // all internal containers and paths and exit this function
0803     if (binRangesMax <= binRangesMin) {
0804         retransform();
0805         return;
0806     }
0807 
0808     const double xMinOld = xMinimum();
0809     const double xMaxOld = xMaximum();
0810     const double yMinOld = yMinimum();
0811     const double yMaxOld = yMaximum();
0812 
0813     // calculate the number of valid data points
0814     int count = 0;
0815     for (int row = 0; row < dataColumn->rowCount(); ++row) {
0816         if (dataColumn->isValid(row) && !dataColumn->isMasked(row))
0817             ++count;
0818     }
0819 
0820     // calculate the number of bins
0821     if (count > 0) {
0822         if (autoBinRanges) {
0823             if (binRangesMin != dataColumn->minimum()) {
0824                 binRangesMin = dataColumn->minimum();
0825                 Q_EMIT q->binRangesMinChanged(binRangesMin);
0826             }
0827 
0828             if (binRangesMax != dataColumn->maximum()) {
0829                 binRangesMax = dataColumn->maximum();
0830                 Q_EMIT q->binRangesMaxChanged(binRangesMax);
0831             }
0832         }
0833 
0834         if (binRangesMin >= binRangesMax) {
0835             Q_EMIT q->dataChanged();
0836             Q_EMIT q->info(i18n("Calculation of the histogram not possible. The max value must be bigger than the min value."));
0837             return;
0838         }
0839 
0840         switch (binningMethod) {
0841         case Histogram::ByNumber:
0842             m_bins = (size_t)binCount;
0843             break;
0844         case Histogram::ByWidth:
0845             m_bins = (size_t)(binRangesMax - binRangesMin) / binWidth;
0846             break;
0847         case Histogram::SquareRoot:
0848             m_bins = (size_t)sqrt(count);
0849             break;
0850         case Histogram::Rice:
0851             m_bins = (size_t)2 * cbrt(count);
0852             break;
0853         case Histogram::Sturges:
0854             m_bins = (size_t)1 + log2(count);
0855             break;
0856         case Histogram::Doane: {
0857             const double skewness = static_cast<const Column*>(dataColumn)->statistics().skewness;
0858             m_bins = (size_t)(1 + log2(count) + log2(1 + abs(skewness) / sqrt((double)6 * (count - 2) / (count + 1) / (count + 3))));
0859             break;
0860         }
0861         case Histogram::Scott: {
0862             const double sigma = static_cast<const Column*>(dataColumn)->statistics().standardDeviation;
0863             const double width = 3.5 * sigma / cbrt(count);
0864             m_bins = (size_t)(binRangesMax - binRangesMin) / width;
0865             break;
0866         }
0867         }
0868 
0869         DEBUG("min " << binRangesMin)
0870         DEBUG("max " << binRangesMax)
0871         DEBUG("number of bins " << m_bins)
0872 
0873         // calculate the histogram
0874         if (m_bins > 0) {
0875             m_histogram = gsl_histogram_alloc(m_bins);
0876             gsl_histogram_set_ranges_uniform(m_histogram, binRangesMin, binRangesMax);
0877 
0878             switch (dataColumn->columnMode()) {
0879             case AbstractColumn::ColumnMode::Double:
0880             case AbstractColumn::ColumnMode::Integer:
0881             case AbstractColumn::ColumnMode::BigInt:
0882                 for (int row = 0; row < dataColumn->rowCount(); ++row) {
0883                     if (dataColumn->isValid(row) && !dataColumn->isMasked(row))
0884                         gsl_histogram_increment(m_histogram, dataColumn->valueAt(row));
0885                 }
0886                 break;
0887             case AbstractColumn::ColumnMode::DateTime:
0888                 for (int row = 0; row < dataColumn->rowCount(); ++row) {
0889                     if (dataColumn->isValid(row) && !dataColumn->isMasked(row))
0890                         gsl_histogram_increment(m_histogram, dataColumn->dateTimeAt(row).toMSecsSinceEpoch());
0891                 }
0892                 break;
0893             case AbstractColumn::ColumnMode::Text:
0894             case AbstractColumn::ColumnMode::Month:
0895             case AbstractColumn::ColumnMode::Day:
0896                 break;
0897             }
0898 
0899             totalCount = 0;
0900             for (size_t i = 0; i < m_bins; ++i)
0901                 totalCount += gsl_histogram_get(m_histogram, i);
0902 
0903             // fill the columns for the positions and values of the bins
0904             if (m_binsColumn) {
0905                 m_binsColumn->resizeTo(m_bins);
0906                 const double width = (binRangesMax - binRangesMin) / m_bins;
0907                 for (size_t i = 0; i < m_bins; ++i)
0908                     m_binsColumn->setValueAt(i, binRangesMin + i * width);
0909             }
0910 
0911             if (m_binValuesColumn) {
0912                 m_binValuesColumn->resizeTo(m_bins);
0913                 double value = 0.;
0914                 for (size_t i = 0; i < m_bins; ++i) {
0915                     histogramValue(value, i);
0916                     m_binValuesColumn->setValueAt(i, value);
0917                 }
0918             }
0919 
0920             if (m_binPDValuesColumn) {
0921                 m_binPDValuesColumn->resizeTo(m_bins);
0922                 const double width = (binRangesMax - binRangesMin) / m_bins;
0923                 for (size_t i = 0; i < m_bins; ++i)
0924                     m_binPDValuesColumn->setValueAt(i, gsl_histogram_get(m_histogram, i) / totalCount / width); // probability density normalization
0925             }
0926         } else
0927             DEBUG("Number of bins must be positive integer")
0928     }
0929 
0930     const double xMin = xMinimum();
0931     const double xMax = xMaximum();
0932     const double yMin = yMinimum();
0933     const double yMax = yMaximum();
0934 
0935     // if the size of the plot has changed because of the actual
0936     // data changes or because of new plot settings, emit dataChanged()
0937     // in order to recalculate the data ranges in the parent plot area
0938     // and to retransform all its children.
0939     // Just call retransform() to update the plot only if the ranges didn't change.
0940     if (xMin != xMinOld || xMax != xMaxOld || yMin != yMinOld || yMax != yMaxOld)
0941         Q_EMIT q->dataChanged();
0942     else
0943         retransform();
0944 }
0945 
0946 void HistogramPrivate::updateType() {
0947     // type (ordinary or cumulative) changed,
0948     // Q_EMIT dataChanged() in order to recalculate everything with the new size/shape of the histogram
0949     Q_EMIT q->dataChanged();
0950 }
0951 
0952 void HistogramPrivate::updateOrientation() {
0953     // orientation (horizontal or vertical) changed
0954     // Q_EMIT dataChanged() in order to recalculate everything with the new size/shape of the histogram
0955     Q_EMIT q->dataChanged();
0956 }
0957 
0958 /*!
0959   recalculates the painter path for the lines connecting the data points.
0960   Called each time when the type of this connection is changed.
0961   */
0962 void HistogramPrivate::updateLines() {
0963     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0964 
0965     linePath = QPainterPath();
0966     lines.clear();
0967     linesUnclipped.clear();
0968     pointsLogical.clear();
0969     pointsScene.clear();
0970 
0971     if (orientation == Histogram::Vertical)
0972         verticalHistogram();
0973     else
0974         horizontalHistogram();
0975 
0976     // map the lines and the symbol positions to the scene coordinates
0977     linesUnclipped = q->cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
0978     lines = q->cSystem->mapLogicalToScene(lines);
0979     visiblePoints = std::vector<bool>(pointsLogical.count(), false);
0980     q->cSystem->mapLogicalToScene(pointsLogical, pointsScene, visiblePoints);
0981 
0982     // new line path
0983     for (const auto& line : lines) {
0984         linePath.moveTo(line.p1());
0985         linePath.lineTo(line.p2());
0986     }
0987 
0988     updateFilling();
0989     recalcShapeAndBoundingRect();
0990 }
0991 
0992 void HistogramPrivate::histogramValue(double& value, int bin) const {
0993     switch (normalization) {
0994     case Histogram::Count:
0995         if (type == Histogram::Ordinary)
0996             value = gsl_histogram_get(m_histogram, bin);
0997         else
0998             value += gsl_histogram_get(m_histogram, bin);
0999         break;
1000     case Histogram::Probability:
1001         if (type == Histogram::Ordinary)
1002             value = gsl_histogram_get(m_histogram, bin) / totalCount;
1003         else
1004             value += gsl_histogram_get(m_histogram, bin) / totalCount;
1005         break;
1006     case Histogram::CountDensity: {
1007         const double width = (binRangesMax - binRangesMin) / m_bins;
1008         if (type == Histogram::Ordinary)
1009             value = gsl_histogram_get(m_histogram, bin) / width;
1010         else
1011             value += gsl_histogram_get(m_histogram, bin) / width;
1012         break;
1013     }
1014     case Histogram::ProbabilityDensity: {
1015         const double width = (binRangesMax - binRangesMin) / m_bins;
1016         if (type == Histogram::Ordinary)
1017             value = gsl_histogram_get(m_histogram, bin) / totalCount / width;
1018         else
1019             value += gsl_histogram_get(m_histogram, bin) / totalCount / width;
1020         break;
1021     }
1022     }
1023 }
1024 
1025 void HistogramPrivate::verticalHistogram() {
1026     if (!m_histogram)
1027         return;
1028 
1029     const double width = (binRangesMax - binRangesMin) / m_bins;
1030     double value = 0.;
1031     const auto lineType = line->histogramLineType();
1032     switch (lineType) {
1033     case Histogram::Bars: {
1034         for (size_t i = 0; i < m_bins; ++i) {
1035             histogramValue(value, i);
1036             const double x = binRangesMin + i * width;
1037             lines.append(QLineF(x, 0., x, value));
1038             lines.append(QLineF(x, value, x + width, value));
1039             lines.append(QLineF(x + width, value, x + width, 0.));
1040             pointsLogical.append(QPointF(x + width / 2, value));
1041         }
1042         break;
1043     }
1044     case Histogram::NoLine:
1045     case Histogram::Envelope: {
1046         double prevValue = 0.;
1047         for (size_t i = 0; i < m_bins; ++i) {
1048             histogramValue(value, i);
1049             const double x = binRangesMin + i * width;
1050             lines.append(QLineF(x, prevValue, x, value));
1051             lines.append(QLineF(x, value, x + width, value));
1052             pointsLogical.append(QPointF(x + width / 2, value));
1053 
1054             if (i == m_bins - 1)
1055                 lines.append(QLineF(x + width, value, x + width, 0.));
1056 
1057             prevValue = value;
1058         }
1059         break;
1060     }
1061     case Histogram::DropLines: {
1062         for (size_t i = 0; i < m_bins; ++i) {
1063             histogramValue(value, i);
1064             const double x = binRangesMin + i * width + width / 2;
1065             lines.append(QLineF(x, 0., x, value));
1066             pointsLogical.append(QPointF(x, value));
1067         }
1068         break;
1069     }
1070     case Histogram::HalfBars: {
1071         for (size_t i = 0; i < m_bins; ++i) {
1072             histogramValue(value, i);
1073             const double x = binRangesMin + i * width + width / 2;
1074             lines.append(QLineF(x, 0., x, value));
1075             lines.append(QLineF(x, value, x - width / 4, value));
1076             pointsLogical.append(QPointF(x, value));
1077         }
1078         break;
1079     }
1080     }
1081 
1082     if (lineType != Histogram::DropLines && lineType != Histogram::HalfBars)
1083         lines.append(QLineF(binRangesMax, 0., binRangesMin, 0.));
1084 }
1085 
1086 void HistogramPrivate::horizontalHistogram() {
1087     if (!m_histogram)
1088         return;
1089 
1090     const double width = (binRangesMax - binRangesMin) / m_bins;
1091     double value = 0.;
1092     const auto lineType = line->histogramLineType();
1093     switch (lineType) {
1094     case Histogram::Bars: {
1095         for (size_t i = 0; i < m_bins; ++i) {
1096             histogramValue(value, i);
1097             const double y = binRangesMin + i * width;
1098             lines.append(QLineF(0., y, value, y));
1099             lines.append(QLineF(value, y, value, y + width));
1100             lines.append(QLineF(value, y + width, 0., y + width));
1101             pointsLogical.append(QPointF(value, y + width / 2));
1102         }
1103         break;
1104     }
1105     case Histogram::NoLine:
1106     case Histogram::Envelope: {
1107         double prevValue = 0.;
1108         for (size_t i = 0; i < m_bins; ++i) {
1109             histogramValue(value, i);
1110             const double y = binRangesMin + i * width;
1111             lines.append(QLineF(prevValue, y, value, y));
1112             lines.append(QLineF(value, y, value, y + width));
1113             pointsLogical.append(QPointF(value, y + width / 2));
1114 
1115             if (i == m_bins - 1)
1116                 lines.append(QLineF(value, y + width, 0., y + width));
1117 
1118             prevValue = value;
1119         }
1120         break;
1121     }
1122     case Histogram::DropLines: {
1123         for (size_t i = 0; i < m_bins; ++i) {
1124             histogramValue(value, i);
1125             const double y = binRangesMin + i * width + width / 2;
1126             lines.append(QLineF(0., y, value, y));
1127             pointsLogical.append(QPointF(value, y));
1128         }
1129         break;
1130     }
1131     case Histogram::HalfBars: {
1132         for (size_t i = 0; i < m_bins; ++i) {
1133             histogramValue(value, i);
1134             const double y = binRangesMin + i * width + width / 2;
1135             lines.append(QLineF(0., y, value, y));
1136             lines.append(QLineF(value, y, value, y + width / 4));
1137             pointsLogical.append(QPointF(value, y));
1138         }
1139         break;
1140     }
1141     }
1142 
1143     if (lineType != Histogram::DropLines && lineType != Histogram::HalfBars)
1144         lines.append(QLineF(0., binRangesMin, 0., binRangesMax));
1145 }
1146 
1147 void HistogramPrivate::updateSymbols() {
1148     symbolsPath = QPainterPath();
1149     if (symbol->style() != Symbol::Style::NoSymbols) {
1150         QPainterPath path = Symbol::stylePath(symbol->style());
1151 
1152         QTransform trafo;
1153         trafo.scale(symbol->size(), symbol->size());
1154         path = trafo.map(path);
1155         trafo.reset();
1156 
1157         if (symbol->rotationAngle() != 0.) {
1158             trafo.rotate(symbol->rotationAngle());
1159             path = trafo.map(path);
1160         }
1161 
1162         for (const auto& point : pointsScene) {
1163             trafo.reset();
1164             trafo.translate(point.x(), point.y());
1165             symbolsPath.addPath(trafo.map(path));
1166         }
1167     }
1168 
1169     recalcShapeAndBoundingRect();
1170 }
1171 
1172 /*!
1173   recreates the value strings to be shown and recalculates their draw position.
1174   */
1175 void HistogramPrivate::updateValues() {
1176     valuesPath = QPainterPath();
1177     valuesPoints.clear();
1178     valuesStrings.clear();
1179 
1180     if (value->type() == Value::NoValues || !m_histogram) {
1181         recalcShapeAndBoundingRect();
1182         return;
1183     }
1184 
1185     // determine the value string for all points that are currently visible in the plot
1186     const auto& valuesPrefix = value->prefix();
1187     const auto& valuesSuffix = value->suffix();
1188     if (value->type() == Value::BinEntries) {
1189         switch (type) {
1190         case Histogram::Ordinary:
1191             for (size_t i = 0; i < m_bins; ++i) {
1192                 if (!visiblePoints[i])
1193                     continue;
1194                 valuesStrings << valuesPrefix + QString::number(gsl_histogram_get(m_histogram, i)) + valuesSuffix;
1195             }
1196             break;
1197         case Histogram::Cumulative: {
1198             int value = 0;
1199             for (size_t i = 0; i < m_bins; ++i) {
1200                 if (!visiblePoints[i])
1201                     continue;
1202                 value += gsl_histogram_get(m_histogram, i);
1203                 valuesStrings << valuesPrefix + QString::number(value) + valuesSuffix;
1204             }
1205             break;
1206         }
1207         case Histogram::AvgShift:
1208             break;
1209         }
1210     } else if (value->type() == Value::CustomColumn) {
1211         const auto* valuesColumn = value->column();
1212         if (!valuesColumn) {
1213             recalcShapeAndBoundingRect();
1214             return;
1215         }
1216 
1217 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1218         const int endRow = std::min(pointsLogical.size(), static_cast<qsizetype>(valuesColumn->rowCount()));
1219 #else
1220         const int endRow = std::min(pointsLogical.size(), valuesColumn->rowCount());
1221 #endif
1222         const auto xColMode = valuesColumn->columnMode();
1223         for (int i = 0; i < endRow; ++i) {
1224             if (!visiblePoints.at(i))
1225                 continue;
1226 
1227             if (!valuesColumn->isValid(i) || valuesColumn->isMasked(i))
1228                 continue;
1229 
1230             switch (xColMode) {
1231             case AbstractColumn::ColumnMode::Double:
1232                 valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i), value->numericFormat(), value->precision()) + valuesSuffix;
1233                 break;
1234             case AbstractColumn::ColumnMode::Integer:
1235             case AbstractColumn::ColumnMode::BigInt:
1236                 valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix;
1237                 break;
1238             case AbstractColumn::ColumnMode::Text:
1239                 valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix;
1240                 break;
1241             case AbstractColumn::ColumnMode::DateTime:
1242             case AbstractColumn::ColumnMode::Month:
1243             case AbstractColumn::ColumnMode::Day:
1244                 valuesStrings << valuesPrefix + valuesColumn->dateTimeAt(i).toString(value->dateTimeFormat()) + valuesSuffix;
1245                 break;
1246             }
1247         }
1248     }
1249 
1250     // Calculate the coordinates where to paint the value strings.
1251     // The coordinates depend on the actual size of the string.
1252     QPointF tempPoint;
1253     QFontMetrics fm(value->font());
1254     qreal w;
1255     const qreal h = fm.ascent();
1256     int valuesDistance = value->distance();
1257     switch (value->position()) {
1258     case Value::Above:
1259         for (int i = 0; i < valuesStrings.size(); i++) {
1260             w = fm.boundingRect(valuesStrings.at(i)).width();
1261             tempPoint.setX(pointsScene.at(i).x() - w / 2);
1262             tempPoint.setY(pointsScene.at(i).y() - valuesDistance);
1263             valuesPoints.append(tempPoint);
1264         }
1265         break;
1266     case Value::Under:
1267         for (int i = 0; i < valuesStrings.size(); i++) {
1268             w = fm.boundingRect(valuesStrings.at(i)).width();
1269             tempPoint.setX(pointsScene.at(i).x() - w / 2);
1270             tempPoint.setY(pointsScene.at(i).y() + valuesDistance + h / 2);
1271             valuesPoints.append(tempPoint);
1272         }
1273         break;
1274     case Value::Left:
1275         for (int i = 0; i < valuesStrings.size(); i++) {
1276             w = fm.boundingRect(valuesStrings.at(i)).width();
1277             tempPoint.setX(pointsScene.at(i).x() - valuesDistance - w - 1);
1278             tempPoint.setY(pointsScene.at(i).y());
1279             valuesPoints.append(tempPoint);
1280         }
1281         break;
1282     case Value::Right:
1283         for (int i = 0; i < valuesStrings.size(); i++) {
1284             tempPoint.setX(pointsScene.at(i).x() + valuesDistance - 1);
1285             tempPoint.setY(pointsScene.at(i).y());
1286             valuesPoints.append(tempPoint);
1287         }
1288         break;
1289     case Value::Center: {
1290         QVector<qreal> listBarWidth;
1291         for (int i = 0, j = 0; i < linesUnclipped.size(); i += 3, j++) {
1292             auto& columnBarLines = linesUnclipped.at(i);
1293             if ((int)(visiblePoints.size()) == j)
1294                 break;
1295             if (visiblePoints.at(j) == true)
1296                 listBarWidth.append(columnBarLines.length());
1297         }
1298         if (orientation == Histogram::Vertical)
1299             for (int i = 0; i < valuesStrings.size(); i++) {
1300                 w = fm.boundingRect(valuesStrings.at(i)).width();
1301                 tempPoint.setX(pointsScene.at(i).x() - w / 2);
1302                 tempPoint.setY(pointsScene.at(i).y() + listBarWidth.at(i) / 2 - valuesDistance + h / 2 + w / 2);
1303                 valuesPoints.append(tempPoint);
1304             }
1305         else {
1306             for (int i = 0; i < valuesStrings.size(); i++) {
1307                 w = fm.boundingRect(valuesStrings.at(i)).width();
1308                 tempPoint.setX(pointsScene.at(i).x() - listBarWidth.at(i) / 2 - valuesDistance + h / 2 - w / 2 - 2);
1309                 tempPoint.setY(pointsScene.at(i).y() + w / 2);
1310                 valuesPoints.append(tempPoint);
1311             }
1312         }
1313         break;
1314     }
1315     }
1316 
1317     QTransform trafo;
1318     QPainterPath path;
1319     double valuesRotationAngle = value->rotationAngle();
1320     for (int i = 0; i < valuesPoints.size(); i++) {
1321         path = QPainterPath();
1322         path.addText(QPoint(0, 0), value->font(), valuesStrings.at(i));
1323 
1324         trafo.reset();
1325         trafo.translate(valuesPoints.at(i).x(), valuesPoints.at(i).y());
1326         if (valuesRotationAngle != 0.)
1327             trafo.rotate(-valuesRotationAngle);
1328 
1329         valuesPath.addPath(trafo.map(path));
1330     }
1331 
1332     recalcShapeAndBoundingRect();
1333 }
1334 
1335 void HistogramPrivate::updateFilling() {
1336     fillPolygon.clear();
1337 
1338     const auto lineType = line->histogramLineType();
1339     if (!background->enabled() || lineType == Histogram::DropLines || lineType == Histogram::HalfBars) {
1340         recalcShapeAndBoundingRect();
1341         return;
1342     }
1343 
1344     const auto& lines = linesUnclipped;
1345     if (lines.isEmpty())
1346         return;
1347 
1348     // clip the line points to the plot data rect and create a new polygon
1349     // out of them that will be filled out.
1350     const QRectF& dataRect = static_cast<CartesianPlot*>(q->parentAspect())->dataRect();
1351     int i = 0;
1352     for (const auto& line : lines) {
1353         // clip the first point of the line
1354         QPointF p1 = line.p1();
1355         if (p1.x() < dataRect.left())
1356             p1.setX(dataRect.left());
1357         else if (p1.x() > dataRect.right())
1358             p1.setX(dataRect.right());
1359 
1360         if (p1.y() < dataRect.top())
1361             p1.setY(dataRect.top());
1362         else if (p1.y() > dataRect.bottom())
1363             p1.setY(dataRect.bottom());
1364 
1365         // clip the second point of the line
1366         QPointF p2 = line.p2();
1367         if (p2.x() < dataRect.left())
1368             p2.setX(dataRect.left());
1369         else if (p2.x() > dataRect.right())
1370             p2.setX(dataRect.right());
1371 
1372         if (p2.y() < dataRect.top())
1373             p2.setY(dataRect.top());
1374         else if (p2.y() > dataRect.bottom())
1375             p2.setY(dataRect.bottom());
1376 
1377         if (i != lines.size() - 1)
1378             fillPolygon << p1;
1379         else {
1380             // close the polygon for the last line,
1381             // take care of the different order for different orientations
1382             if (orientation == Histogram::Vertical) {
1383                 fillPolygon << p1;
1384                 fillPolygon << p2;
1385             } else {
1386                 fillPolygon << p2;
1387                 fillPolygon << p1;
1388             }
1389         }
1390 
1391         ++i;
1392     }
1393 
1394     recalcShapeAndBoundingRect();
1395 }
1396 
1397 void HistogramPrivate::updateErrorBars() {
1398     errorBarsPath = QPainterPath();
1399 
1400     QVector<QLineF> elines;
1401 
1402     switch (errorBar->type()) {
1403     case ErrorBar::Type::NoError:
1404         break;
1405     case ErrorBar::Type::Poisson: {
1406         if (orientation == Histogram::Vertical) {
1407             for (auto& point : pointsLogical) {
1408                 double error = sqrt(point.y());
1409                 if (error != 0.)
1410                     elines << QLineF(point.x(), point.y() + error, point.x(), point.y() - error);
1411             }
1412         } else {
1413             for (auto& point : pointsLogical) {
1414                 double error = sqrt(point.x());
1415                 if (error != 0.)
1416                     elines << QLineF(point.x() - error, point.y(), point.x() + error, point.y());
1417             }
1418         }
1419         break;
1420     }
1421     case ErrorBar::Type::Symmetric: {
1422         int index = 0;
1423         if (orientation == Histogram::Vertical) {
1424             const auto* errorPlusColumn = errorBar->plusColumn();
1425             for (auto& point : pointsLogical) {
1426                 if (errorPlusColumn && errorPlusColumn->isValid(index) && !errorPlusColumn->isMasked(index)) {
1427                     double error = errorPlusColumn->valueAt(index);
1428                     if (error != 0.)
1429                         elines << QLineF(point.x(), point.y() + error, point.x(), point.y() - error);
1430                 }
1431                 ++index;
1432             }
1433         } else {
1434             const auto* errorMinusColumn = errorBar->minusColumn();
1435             for (auto& point : pointsLogical) {
1436                 if (errorMinusColumn && errorMinusColumn->isValid(index) && !errorMinusColumn->isMasked(index)) {
1437                     double error = errorMinusColumn->valueAt(index);
1438                     if (error != 0.)
1439                         elines << QLineF(point.x() - error, point.y(), point.x() + error, point.y());
1440                 }
1441                 ++index;
1442             }
1443         }
1444         break;
1445     }
1446     case ErrorBar::Type::Asymmetric: {
1447         int index = 0;
1448         if (orientation == Histogram::Vertical) {
1449             for (auto& point : pointsLogical) {
1450                 double errorPlus = 0.;
1451                 double errorMinus = 0.;
1452                 const auto* errorPlusColumn = errorBar->plusColumn();
1453                 const auto* errorMinusColumn = errorBar->minusColumn();
1454 
1455                 if (errorPlusColumn && errorPlusColumn->isValid(index) && !errorPlusColumn->isMasked(index))
1456                     errorPlus = errorPlusColumn->valueAt(index);
1457 
1458                 if (errorMinusColumn && errorMinusColumn->isValid(index) && !errorMinusColumn->isMasked(index))
1459                     errorMinus = errorMinusColumn->valueAt(index);
1460 
1461                 if (errorPlus != 0. || errorMinus != 0.)
1462                     elines << QLineF(point.x(), point.y() - errorMinus, point.x(), point.y() + errorPlus);
1463 
1464                 ++index;
1465             }
1466         } else {
1467             for (auto& point : pointsLogical) {
1468                 double errorPlus = 0.;
1469                 double errorMinus = 0.;
1470                 const auto* errorPlusColumn = errorBar->plusColumn();
1471                 const auto* errorMinusColumn = errorBar->minusColumn();
1472 
1473                 if (errorPlusColumn && errorPlusColumn->isValid(index) && !errorPlusColumn->isMasked(index))
1474                     errorPlus = errorPlusColumn->valueAt(index);
1475 
1476                 if (errorMinusColumn && errorMinusColumn->isValid(index) && !errorMinusColumn->isMasked(index))
1477                     errorMinus = errorMinusColumn->valueAt(index);
1478 
1479                 if (errorPlus != 0. || errorMinus != 0.)
1480                     elines << QLineF(point.x() - errorMinus, point.y(), point.x() + errorPlus, point.y());
1481 
1482                 ++index;
1483             }
1484         }
1485         break;
1486     }
1487     }
1488 
1489     // map the error bars to scene coordinates
1490     elines = q->cSystem->mapLogicalToScene(elines);
1491 
1492     // new painter path for the error bars
1493     for (const auto& line : qAsConst(elines)) {
1494         errorBarsPath.moveTo(line.p1());
1495         errorBarsPath.lineTo(line.p2());
1496     }
1497 
1498     // add caps for error bars
1499     const auto errorBarsCapSize = errorBarStyle->capSize();
1500     if (errorBarStyle->type() == ErrorBarStyle::Type::WithEnds) {
1501         if (orientation == Histogram::Vertical) {
1502             for (const auto& line : qAsConst(elines)) {
1503                 const auto& p1 = line.p1();
1504                 errorBarsPath.moveTo(QPointF(p1.x() - errorBarsCapSize / 2., p1.y()));
1505                 errorBarsPath.lineTo(QPointF(p1.x() + errorBarsCapSize / 2., p1.y()));
1506 
1507                 const auto& p2 = line.p2();
1508                 errorBarsPath.moveTo(QPointF(p2.x() - errorBarsCapSize / 2., p2.y()));
1509                 errorBarsPath.lineTo(QPointF(p2.x() + errorBarsCapSize / 2., p2.y()));
1510             }
1511         } else {
1512             for (const auto& line : qAsConst(elines)) {
1513                 const auto& p1 = line.p1();
1514                 errorBarsPath.moveTo(QPointF(p1.x(), p1.y() - errorBarsCapSize / 2.));
1515                 errorBarsPath.lineTo(QPointF(p1.x(), p1.y() + errorBarsCapSize / 2.));
1516 
1517                 const auto& p2 = line.p2();
1518                 errorBarsPath.moveTo(QPointF(p2.x(), p2.y() - errorBarsCapSize / 2.));
1519                 errorBarsPath.lineTo(QPointF(p2.x(), p2.y() + errorBarsCapSize / 2.));
1520             }
1521         }
1522     }
1523 
1524     recalcShapeAndBoundingRect();
1525 }
1526 
1527 void HistogramPrivate::updateRug() {
1528     rugPath = QPainterPath();
1529 
1530     if (!rugEnabled || !q->plot()) {
1531         recalcShapeAndBoundingRect();
1532         return;
1533     }
1534 
1535     QVector<QPointF> points;
1536     auto cs = q->plot()->coordinateSystem(q->coordinateSystemIndex());
1537     const double xMin = q->plot()->range(Dimension::X, cs->index(Dimension::X)).start();
1538     const double yMin = q->plot()->range(Dimension::Y, cs->index(Dimension::Y)).start();
1539 
1540     if (orientation == Histogram::Vertical) {
1541         for (int row = 0; row < dataColumn->rowCount(); ++row) {
1542             if (dataColumn->isValid(row) && !dataColumn->isMasked(row))
1543                 points << QPointF(dataColumn->valueAt(row), yMin);
1544         }
1545 
1546         // map the points to scene coordinates
1547         points = q->cSystem->mapLogicalToScene(points);
1548 
1549         // path for the vertical rug lines
1550         for (const auto& point : qAsConst(points)) {
1551             rugPath.moveTo(point.x(), point.y() - rugOffset);
1552             rugPath.lineTo(point.x(), point.y() - rugOffset - rugLength);
1553         }
1554     } else {
1555         for (int row = 0; row < dataColumn->rowCount(); ++row) {
1556             if (dataColumn->isValid(row) && !dataColumn->isMasked(row))
1557                 points << QPointF(xMin, dataColumn->valueAt(row));
1558         }
1559 
1560         // map the points to scene coordinates
1561         points = q->cSystem->mapLogicalToScene(points);
1562 
1563         // path for the horizontal rug lines
1564         for (const auto& point : qAsConst(points)) {
1565             rugPath.moveTo(point.x() + rugOffset, point.y());
1566             rugPath.lineTo(point.x() + rugOffset + rugLength, point.y());
1567         }
1568     }
1569 
1570     recalcShapeAndBoundingRect();
1571 }
1572 
1573 /*!
1574   recalculates the outer bounds and the shape of the curve.
1575   */
1576 void HistogramPrivate::recalcShapeAndBoundingRect() {
1577     if (suppressRecalc)
1578         return;
1579 
1580     prepareGeometryChange();
1581     m_shape = QPainterPath();
1582     if (line->histogramLineType() != Histogram::NoLine)
1583         m_shape.addPath(WorksheetElement::shapeFromPath(linePath, line->pen()));
1584 
1585     if (symbol->style() != Symbol::Style::NoSymbols)
1586         m_shape.addPath(symbolsPath);
1587 
1588     if (value->type() != Value::NoValues)
1589         m_shape.addPath(valuesPath);
1590 
1591     if (errorBar->type() != ErrorBar::Type::NoError)
1592         m_shape.addPath(WorksheetElement::shapeFromPath(errorBarsPath, errorBarStyle->line()->pen()));
1593 
1594     m_shape.addPath(rugPath);
1595     m_shape.addPolygon(fillPolygon);
1596 
1597     m_boundingRectangle = m_shape.boundingRect();
1598     m_boundingRectangle = m_boundingRectangle.united(fillPolygon.boundingRect());
1599 
1600     // TODO: when the selection is painted, line intersections are visible.
1601     // simplified() removes those artifacts but is horrible slow for curves with large number of points.
1602     // search for an alternative.
1603     // m_shape = m_shape.simplified();
1604 
1605     updatePixmap();
1606 }
1607 
1608 void HistogramPrivate::draw(QPainter* painter) {
1609     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
1610 
1611     // drawing line
1612     if (line->histogramLineType() != Histogram::NoLine) {
1613         painter->setOpacity(line->opacity());
1614         painter->setPen(line->pen());
1615         painter->setBrush(Qt::NoBrush);
1616         painter->drawPath(linePath);
1617     }
1618 
1619     // draw filling
1620     if (background->enabled()) {
1621         painter->setOpacity(background->opacity());
1622         painter->setPen(Qt::NoPen);
1623         drawFillingPollygon(fillPolygon, painter, background);
1624     }
1625 
1626     // draw symbols
1627     symbol->draw(painter, pointsScene);
1628 
1629     // draw values
1630     value->draw(painter, valuesPoints, valuesStrings);
1631 
1632     // draw error bars
1633     if (errorBar->type() != ErrorBar::Type::NoError) {
1634         painter->setOpacity(errorBarStyle->line()->opacity());
1635         painter->setPen(errorBarStyle->line()->pen());
1636         painter->setBrush(Qt::NoBrush);
1637         painter->drawPath(errorBarsPath);
1638     }
1639 
1640     // draw rug
1641     if (rugEnabled) {
1642         QPen pen;
1643         pen.setColor(line->pen().color());
1644         pen.setWidthF(rugWidth);
1645         painter->setPen(pen);
1646         painter->setOpacity(line->opacity());
1647         painter->drawPath(rugPath);
1648     }
1649 }
1650 
1651 void HistogramPrivate::updatePixmap() {
1652     QPixmap pixmap(m_boundingRectangle.width(), m_boundingRectangle.height());
1653     if (m_boundingRectangle.width() == 0. || m_boundingRectangle.height() == 0.) {
1654         m_pixmap = pixmap;
1655         m_hoverEffectImageIsDirty = true;
1656         m_selectionEffectImageIsDirty = true;
1657         return;
1658     }
1659     pixmap.fill(Qt::transparent);
1660     QPainter painter(&pixmap);
1661     painter.setRenderHint(QPainter::Antialiasing, true);
1662     painter.translate(-m_boundingRectangle.topLeft());
1663 
1664     draw(&painter);
1665     painter.end();
1666 
1667     m_pixmap = pixmap;
1668     m_hoverEffectImageIsDirty = true;
1669     m_selectionEffectImageIsDirty = true;
1670     Q_EMIT q->changed();
1671     update();
1672 }
1673 
1674 /*!
1675   Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the curve.
1676   \sa QGraphicsItem::paint().
1677   */
1678 void HistogramPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) {
1679     if (!isVisible())
1680         return;
1681 
1682     painter->setPen(Qt::NoPen);
1683     painter->setBrush(Qt::NoBrush);
1684     painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
1685 
1686     if (Settings::group(QStringLiteral("Settings_Worksheet")).readEntry<bool>("DoubleBuffering", true))
1687         painter->drawPixmap(m_boundingRectangle.topLeft(), m_pixmap); // draw the cached pixmap (fast)
1688     else
1689         draw(painter); // draw directly again (slow)
1690 
1691     if (m_hovered && !isSelected() && !q->isPrinting()) {
1692         if (m_hoverEffectImageIsDirty) {
1693             QPixmap pix = m_pixmap;
1694             QPainter p(&pix);
1695             p.setCompositionMode(QPainter::CompositionMode_SourceIn); // source (shadow) pixels merged with the alpha channel of the destination (m_pixmap)
1696             p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Shadow));
1697             p.end();
1698 
1699             m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5);
1700             m_hoverEffectImageIsDirty = false;
1701         }
1702 
1703         painter->drawImage(m_boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect());
1704         return;
1705     }
1706 
1707     if (isSelected() && !q->isPrinting()) {
1708         if (m_selectionEffectImageIsDirty) {
1709             QPixmap pix = m_pixmap;
1710             QPainter p(&pix);
1711             p.setCompositionMode(QPainter::CompositionMode_SourceIn);
1712             p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Highlight));
1713             p.end();
1714 
1715             m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5);
1716             m_selectionEffectImageIsDirty = false;
1717         }
1718 
1719         painter->drawImage(m_boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect());
1720         return;
1721     }
1722 }
1723 
1724 void HistogramPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) {
1725     const auto* plot = static_cast<const CartesianPlot*>(q->parentAspect());
1726     if (plot->mouseMode() == CartesianPlot::MouseMode::Selection && !isSelected())
1727         setHover(true);
1728 }
1729 
1730 void HistogramPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) {
1731     const auto* plot = static_cast<const CartesianPlot*>(q->parentAspect());
1732     if (plot->mouseMode() == CartesianPlot::MouseMode::Selection && m_hovered) {
1733         setHover(false);
1734     }
1735 }
1736 
1737 /*!
1738  * checks if the mousePress event was done near the histogram shape
1739  * and selects the graphics item if it is the case.
1740  * \p event
1741  */
1742 void HistogramPrivate::mousePressEvent(QGraphicsSceneMouseEvent* event) {
1743     if (static_cast<const CartesianPlot*>(q->parentAspect())->mouseMode() != CartesianPlot::MouseMode::Selection) {
1744         event->ignore();
1745         return QGraphicsItem::mousePressEvent(event);
1746     }
1747 
1748     if (q->activatePlot(event->pos())) {
1749         setSelected(true);
1750         return;
1751     }
1752 
1753     event->ignore();
1754     setSelected(false);
1755     QGraphicsItem::mousePressEvent(event);
1756 }
1757 
1758 // ##############################################################################
1759 // ##################  Serialization/Deserialization  ###########################
1760 // ##############################################################################
1761 //! Save as XML
1762 void Histogram::save(QXmlStreamWriter* writer) const {
1763     Q_D(const Histogram);
1764 
1765     writer->writeStartElement(QStringLiteral("Histogram"));
1766     writeBasicAttributes(writer);
1767     writeCommentElement(writer);
1768 
1769     // general
1770     writer->writeStartElement(QStringLiteral("general"));
1771     WRITE_COLUMN(d->dataColumn, dataColumn);
1772     writer->writeAttribute(QStringLiteral("type"), QString::number(d->type));
1773     writer->writeAttribute(QStringLiteral("orientation"), QString::number(d->orientation));
1774     writer->writeAttribute(QStringLiteral("normalization"), QString::number(d->normalization));
1775     writer->writeAttribute(QStringLiteral("binningMethod"), QString::number(d->binningMethod));
1776     writer->writeAttribute(QStringLiteral("binCount"), QString::number(d->binCount));
1777     writer->writeAttribute(QStringLiteral("binWidth"), QString::number(d->binWidth));
1778     writer->writeAttribute(QStringLiteral("autoBinRanges"), QString::number(d->autoBinRanges));
1779     writer->writeAttribute(QStringLiteral("binRangesMin"), QString::number(d->binRangesMin));
1780     writer->writeAttribute(QStringLiteral("binRangesMax"), QString::number(d->binRangesMax));
1781     writer->writeAttribute(QStringLiteral("plotRangeIndex"), QString::number(m_cSystemIndex));
1782     writer->writeAttribute(QStringLiteral("legendVisible"), QString::number(d->legendVisible));
1783     writer->writeAttribute(QStringLiteral("visible"), QString::number(d->isVisible()));
1784     writer->writeEndElement();
1785 
1786     d->background->save(writer);
1787     d->line->save(writer);
1788     d->symbol->save(writer);
1789     d->value->save(writer);
1790 
1791     // Error bars
1792     writer->writeStartElement(QStringLiteral("errorBars"));
1793     d->errorBar->save(writer);
1794     d->errorBarStyle->save(writer);
1795     writer->writeEndElement();
1796 
1797     // margin plots
1798     writer->writeStartElement(QStringLiteral("margins"));
1799     writer->writeAttribute(QStringLiteral("rugEnabled"), QString::number(d->rugEnabled));
1800     writer->writeAttribute(QStringLiteral("rugLength"), QString::number(d->rugLength));
1801     writer->writeAttribute(QStringLiteral("rugWidth"), QString::number(d->rugWidth));
1802     writer->writeAttribute(QStringLiteral("rugOffset"), QString::number(d->rugOffset));
1803     writer->writeEndElement();
1804 
1805     writer->writeEndElement(); // close "Histogram" section
1806 }
1807 
1808 //! Load from XML
1809 bool Histogram::load(XmlStreamReader* reader, bool preview) {
1810     Q_D(Histogram);
1811 
1812     if (!readBasicAttributes(reader))
1813         return false;
1814 
1815     QXmlStreamAttributes attribs;
1816     QString str;
1817 
1818     while (!reader->atEnd()) {
1819         reader->readNext();
1820         if (reader->isEndElement() && reader->name() == QLatin1String("Histogram"))
1821             break;
1822 
1823         if (!reader->isStartElement())
1824             continue;
1825 
1826         if (reader->name() == QLatin1String("comment")) {
1827             if (!readCommentElement(reader))
1828                 return false;
1829         } else if (!preview && reader->name() == QLatin1String("general")) {
1830             attribs = reader->attributes();
1831 
1832             READ_COLUMN(dataColumn);
1833             READ_INT_VALUE("type", type, Histogram::Type);
1834             READ_INT_VALUE("orientation", orientation, Histogram::Orientation);
1835             READ_INT_VALUE("normalization", normalization, Histogram::Normalization);
1836             READ_INT_VALUE("binningMethod", binningMethod, Histogram::BinningMethod);
1837             READ_INT_VALUE("binCount", binCount, int);
1838             READ_DOUBLE_VALUE("binWidth", binWidth);
1839             READ_INT_VALUE("autoBinRanges", autoBinRanges, bool);
1840             READ_DOUBLE_VALUE("binRangesMin", binRangesMin);
1841             READ_DOUBLE_VALUE("binRangesMax", binRangesMax);
1842             READ_INT_VALUE_DIRECT("plotRangeIndex", m_cSystemIndex, int);
1843             READ_INT_VALUE("legendVisible", legendVisible, bool);
1844 
1845             str = attribs.value(QStringLiteral("visible")).toString();
1846             if (str.isEmpty())
1847                 reader->raiseMissingAttributeWarning(QStringLiteral("visible"));
1848             else
1849                 d->setVisible(str.toInt());
1850         } else if (!preview && reader->name() == QLatin1String("line")) {
1851             d->line->load(reader, preview);
1852         } else if (!preview && reader->name() == QLatin1String("symbols"))
1853             d->symbol->load(reader, preview);
1854         else if (!preview && reader->name() == QLatin1String("values"))
1855             d->value->load(reader, preview);
1856         else if (!preview && reader->name() == QLatin1String("filling"))
1857             d->background->load(reader, preview);
1858         else if (!preview && reader->name() == QLatin1String("errorBars")) {
1859             d->errorBar->load(reader, preview);
1860             d->errorBarStyle->load(reader, preview);
1861         } else if (!preview && reader->name() == QLatin1String("margins")) {
1862             attribs = reader->attributes();
1863 
1864             READ_INT_VALUE("rugEnabled", rugEnabled, bool);
1865             READ_DOUBLE_VALUE("rugLength", rugLength);
1866             READ_DOUBLE_VALUE("rugWidth", rugWidth);
1867             READ_DOUBLE_VALUE("rugOffset", rugOffset);
1868         } else { // unknown element
1869             reader->raiseUnknownElementWarning();
1870             if (!reader->skipToEndElement())
1871                 return false;
1872         }
1873     }
1874     return true;
1875 }
1876 
1877 // ##############################################################################
1878 // #########################  Theme management ##################################
1879 // ##############################################################################
1880 void Histogram::loadThemeConfig(const KConfig& config) {
1881     KConfigGroup group;
1882     if (config.hasGroup(QStringLiteral("Theme")))
1883         group = config.group(QStringLiteral("XYCurve")); // when loading from the theme config, use the same properties as for XYCurve
1884     else
1885         group = config.group(QStringLiteral("Histogram"));
1886 
1887     const auto* plot = static_cast<const CartesianPlot*>(parentAspect());
1888     int index = plot->curveChildIndex(this);
1889     const QColor themeColor = plot->themeColorPalette(index);
1890 
1891     QPen p;
1892 
1893     Q_D(Histogram);
1894     d->suppressRecalc = true;
1895 
1896     d->line->loadThemeConfig(group, themeColor);
1897     d->symbol->loadThemeConfig(group, themeColor);
1898     d->value->loadThemeConfig(group, themeColor);
1899     d->background->loadThemeConfig(group, themeColor);
1900     d->errorBarStyle->loadThemeConfig(group, themeColor);
1901 
1902     if (plot->theme() == QLatin1String("Tufte")) {
1903         d->line->setHistogramLineType(Histogram::LineType::HalfBars);
1904         if (d->dataColumn && d->dataColumn->rowCount() < 100)
1905             setRugEnabled(true);
1906     } else
1907         setRugEnabled(false);
1908 
1909     d->suppressRecalc = false;
1910     d->recalcShapeAndBoundingRect();
1911 }
1912 
1913 void Histogram::saveThemeConfig(const KConfig& config) {
1914     Q_D(const Histogram);
1915     KConfigGroup group = config.group(QStringLiteral("Histogram"));
1916 
1917     d->line->saveThemeConfig(group);
1918     d->symbol->saveThemeConfig(group);
1919     d->value->saveThemeConfig(group);
1920     d->background->saveThemeConfig(group);
1921     d->errorBarStyle->saveThemeConfig(group);
1922 
1923     int index = parentAspect()->indexOfChild<Histogram>(this);
1924     if (index < 5) {
1925         KConfigGroup themeGroup = config.group(QStringLiteral("Theme"));
1926         for (int i = index; i < 5; i++) {
1927             QString s = QStringLiteral("ThemePaletteColor") + QString::number(i + 1);
1928             themeGroup.writeEntry(s, d->line->pen().color());
1929         }
1930     }
1931 }