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 }