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

0001 /*
0002     File                 : BarPlot.cpp
0003     Project              : LabPlot
0004     Description          : Bar Plot
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2022 Alexander Semke <alexander.semke@web.de>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "BarPlot.h"
0011 #include "BarPlotPrivate.h"
0012 #include "backend/core/AbstractColumn.h"
0013 #include "backend/core/Settings.h"
0014 #include "backend/core/column/Column.h"
0015 #include "backend/lib/XmlStreamReader.h"
0016 #include "backend/lib/commandtemplates.h"
0017 #include "backend/lib/trace.h"
0018 #include "backend/worksheet/Background.h"
0019 #include "backend/worksheet/Line.h"
0020 #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h"
0021 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0022 #include "backend/worksheet/plots/cartesian/Value.h"
0023 #include "tools/ImageTools.h"
0024 
0025 #include <QActionGroup>
0026 #include <QApplication>
0027 #include <QGraphicsSceneMouseEvent>
0028 #include <QMenu>
0029 #include <QPainter>
0030 
0031 #include <KConfig>
0032 #include <KConfigGroup>
0033 #include <KLocalizedString>
0034 
0035 /**
0036  * \class BarPlot
0037  * \brief Box Plot
0038  */
0039 
0040 BarPlot::BarPlot(const QString& name)
0041     : Plot(name, new BarPlotPrivate(this), AspectType::BarPlot) {
0042     init();
0043 }
0044 
0045 BarPlot::BarPlot(const QString& name, BarPlotPrivate* dd)
0046     : Plot(name, dd, AspectType::BarPlot) {
0047     init();
0048 }
0049 
0050 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0051 // and is deleted during the cleanup in QGraphicsScene
0052 BarPlot::~BarPlot() = default;
0053 
0054 void BarPlot::init() {
0055     Q_D(BarPlot);
0056 
0057     KConfig config;
0058     const auto& group = config.group(QStringLiteral("BarPlot"));
0059 
0060     // general
0061     d->type = (BarPlot::Type)group.readEntry(QStringLiteral("Type"), (int)BarPlot::Type::Grouped);
0062     d->orientation = (BarPlot::Orientation)group.readEntry(QStringLiteral("Orientation"), (int)BarPlot::Orientation::Vertical);
0063     d->widthFactor = group.readEntry(QStringLiteral("WidthFactor"), 1.0);
0064 
0065     // initial background and border line objects that will be available even if not data column was set yet
0066     d->addBackground(group);
0067     d->addBorderLine(group);
0068 
0069     // values
0070     d->addValue(group);
0071 }
0072 
0073 /*!
0074     Returns an icon to be used in the project explorer.
0075 */
0076 QIcon BarPlot::icon() const {
0077     return QIcon::fromTheme(QStringLiteral("office-chart-bar"));
0078 }
0079 
0080 void BarPlot::initActions() {
0081     // Orientation
0082     auto* orientationActionGroup = new QActionGroup(this);
0083     orientationActionGroup->setExclusive(true);
0084     connect(orientationActionGroup, &QActionGroup::triggered, this, &BarPlot::orientationChangedSlot);
0085 
0086     orientationHorizontalAction = new QAction(QIcon::fromTheme(QStringLiteral("transform-move-horizontal")), i18n("Horizontal"), orientationActionGroup);
0087     orientationHorizontalAction->setCheckable(true);
0088 
0089     orientationVerticalAction = new QAction(QIcon::fromTheme(QStringLiteral("transform-move-vertical")), i18n("Vertical"), orientationActionGroup);
0090     orientationVerticalAction->setCheckable(true);
0091 }
0092 
0093 void BarPlot::initMenus() {
0094     this->initActions();
0095 
0096     // Orientation
0097     orientationMenu = new QMenu(i18n("Orientation"));
0098     orientationMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-cross")));
0099     orientationMenu->addAction(orientationHorizontalAction);
0100     orientationMenu->addAction(orientationVerticalAction);
0101 }
0102 
0103 QMenu* BarPlot::createContextMenu() {
0104     if (!orientationMenu)
0105         initMenus();
0106 
0107     QMenu* menu = WorksheetElement::createContextMenu();
0108     QAction* visibilityAction = this->visibilityAction();
0109 
0110     // Orientation
0111     Q_D(const BarPlot);
0112     if (d->orientation == Orientation::Horizontal)
0113         orientationHorizontalAction->setChecked(true);
0114     else
0115         orientationVerticalAction->setChecked(true);
0116     menu->insertMenu(visibilityAction, orientationMenu);
0117     menu->insertSeparator(visibilityAction);
0118 
0119     return menu;
0120 }
0121 
0122 void BarPlot::retransform() {
0123     Q_D(BarPlot);
0124     d->retransform();
0125 }
0126 
0127 void BarPlot::recalc() {
0128     Q_D(BarPlot);
0129     d->recalc();
0130 }
0131 
0132 void BarPlot::handleResize(double /*horizontalRatio*/, double /*verticalRatio*/, bool /*pageResize*/) {
0133 }
0134 
0135 /* ============================ getter methods ================= */
0136 // general
0137 BASIC_SHARED_D_READER_IMPL(BarPlot, QVector<const AbstractColumn*>, dataColumns, dataColumns)
0138 BASIC_SHARED_D_READER_IMPL(BarPlot, BarPlot::Type, type, type)
0139 BASIC_SHARED_D_READER_IMPL(BarPlot, BarPlot::Orientation, orientation, orientation)
0140 BASIC_SHARED_D_READER_IMPL(BarPlot, double, widthFactor, widthFactor)
0141 BASIC_SHARED_D_READER_IMPL(BarPlot, const AbstractColumn*, xColumn, xColumn)
0142 
0143 QString& BarPlot::xColumnPath() const {
0144     D(BarPlot);
0145     return d->xColumnPath;
0146 }
0147 
0148 // box filling
0149 Background* BarPlot::backgroundAt(int index) const {
0150     Q_D(const BarPlot);
0151     if (index < d->backgrounds.size())
0152         return d->backgrounds.at(index);
0153     else
0154         return nullptr;
0155 }
0156 
0157 // box border lines
0158 Line* BarPlot::lineAt(int index) const {
0159     Q_D(const BarPlot);
0160     if (index < d->borderLines.size())
0161         return d->borderLines.at(index);
0162     else
0163         return nullptr;
0164 }
0165 
0166 QVector<QString>& BarPlot::dataColumnPaths() const {
0167     D(BarPlot);
0168     return d->dataColumnPaths;
0169 }
0170 
0171 double BarPlot::minimum(const Dimension dim) const {
0172     Q_D(const BarPlot);
0173     switch (dim) {
0174     case Dimension::X:
0175         return d->xMin;
0176     case Dimension::Y:
0177         return d->yMin;
0178     }
0179     return NAN;
0180 }
0181 
0182 double BarPlot::maximum(const Dimension dim) const {
0183     Q_D(const BarPlot);
0184     switch (dim) {
0185     case Dimension::X:
0186         return d->xMax;
0187     case Dimension::Y:
0188         return d->yMax;
0189     }
0190     return NAN;
0191 }
0192 
0193 bool BarPlot::hasData() const {
0194     Q_D(const BarPlot);
0195     return !d->dataColumns.isEmpty();
0196 }
0197 
0198 bool BarPlot::usingColumn(const Column* column) const {
0199     Q_D(const BarPlot);
0200 
0201     if (d->xColumn == column)
0202         return true;
0203 
0204     for (auto* c : d->dataColumns) {
0205         if (c == column)
0206             return true;
0207     }
0208 
0209     return false;
0210 }
0211 
0212 void BarPlot::updateColumnDependencies(const AbstractColumn* column) {
0213     Q_D(const BarPlot);
0214     const QString& columnPath = column->path();
0215     const auto dataColumnPaths = d->dataColumnPaths;
0216     auto dataColumns = d->dataColumns;
0217     bool changed = false;
0218 
0219     for (int i = 0; i < dataColumnPaths.count(); ++i) {
0220         const auto& path = dataColumnPaths.at(i);
0221 
0222         if (path == columnPath) {
0223             dataColumns[i] = column;
0224             changed = true;
0225         }
0226     }
0227 
0228     if (changed) {
0229         setUndoAware(false);
0230         setDataColumns(dataColumns);
0231         setUndoAware(true);
0232     }
0233 }
0234 
0235 QColor BarPlot::color() const {
0236     Q_D(const BarPlot);
0237     if (d->backgrounds.size() > 0 && d->backgrounds.at(0)->enabled())
0238         return d->backgrounds.at(0)->firstColor();
0239     else if (d->borderLines.size() > 0 && d->borderLines.at(0)->style() != Qt::PenStyle::NoPen)
0240         return d->borderLines.at(0)->pen().color();
0241     return QColor();
0242 }
0243 
0244 // values
0245 Value* BarPlot::value() const {
0246     Q_D(const BarPlot);
0247     return d->value;
0248 }
0249 
0250 /* ============================ setter methods and undo commands ================= */
0251 
0252 // General
0253 STD_SETTER_CMD_IMPL_F_S(BarPlot, SetXColumn, const AbstractColumn*, xColumn, recalc)
0254 void BarPlot::setXColumn(const AbstractColumn* column) {
0255     Q_D(BarPlot);
0256     if (column != d->xColumn) {
0257         exec(new BarPlotSetXColumnCmd(d, column, ki18n("%1: set x column")));
0258 
0259         if (column) {
0260             // update the curve itself on changes
0261             connect(column, &AbstractColumn::dataChanged, this, &BarPlot::recalc);
0262             if (column->parentAspect())
0263                 connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &BarPlot::dataColumnAboutToBeRemoved);
0264 
0265             connect(column, &AbstractColumn::dataChanged, this, &BarPlot::dataChanged);
0266             // TODO: add disconnect in the undo-function
0267         }
0268     }
0269 }
0270 
0271 STD_SETTER_CMD_IMPL_F_S(BarPlot, SetDataColumns, QVector<const AbstractColumn*>, dataColumns, recalc)
0272 void BarPlot::setDataColumns(const QVector<const AbstractColumn*> columns) {
0273     Q_D(BarPlot);
0274     if (columns != d->dataColumns) {
0275         exec(new BarPlotSetDataColumnsCmd(d, columns, ki18n("%1: set data columns")));
0276 
0277         for (auto* column : columns) {
0278             if (!column)
0279                 continue;
0280 
0281             // update the curve itself on changes
0282             connect(column, &AbstractColumn::dataChanged, this, &BarPlot::recalc);
0283             if (column->parentAspect())
0284                 connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &BarPlot::dataColumnAboutToBeRemoved);
0285             // TODO: add disconnect in the undo-function
0286 
0287             connect(column, &AbstractColumn::dataChanged, this, &BarPlot::dataChanged);
0288             connect(column, &AbstractAspect::aspectDescriptionChanged, this, &Plot::appearanceChanged);
0289         }
0290     }
0291 }
0292 
0293 STD_SETTER_CMD_IMPL_F_S(BarPlot, SetType, BarPlot::Type, type, recalc)
0294 void BarPlot::setType(BarPlot::Type type) {
0295     Q_D(BarPlot);
0296     if (type != d->type)
0297         exec(new BarPlotSetTypeCmd(d, type, ki18n("%1: set type")));
0298 }
0299 
0300 STD_SETTER_CMD_IMPL_F_S(BarPlot, SetOrientation, BarPlot::Orientation, orientation, recalc)
0301 void BarPlot::setOrientation(BarPlot::Orientation orientation) {
0302     Q_D(BarPlot);
0303     if (orientation != d->orientation)
0304         exec(new BarPlotSetOrientationCmd(d, orientation, ki18n("%1: set orientation")));
0305 }
0306 
0307 STD_SETTER_CMD_IMPL_F_S(BarPlot, SetWidthFactor, double, widthFactor, recalc)
0308 void BarPlot::setWidthFactor(double widthFactor) {
0309     Q_D(BarPlot);
0310     if (widthFactor != d->widthFactor)
0311         exec(new BarPlotSetWidthFactorCmd(d, widthFactor, ki18n("%1: width factor changed")));
0312 }
0313 
0314 // ##############################################################################
0315 // #################################  SLOTS  ####################################
0316 // ##############################################################################
0317 
0318 void BarPlot::dataColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0319     Q_D(BarPlot);
0320     for (int i = 0; i < d->dataColumns.size(); ++i) {
0321         if (aspect == d->dataColumns.at(i)) {
0322             d->dataColumns[i] = nullptr;
0323             d->retransform();
0324             break;
0325         }
0326     }
0327 }
0328 
0329 // ##############################################################################
0330 // ######  SLOTs for changes triggered via QActions in the context menu  ########
0331 // ##############################################################################
0332 void BarPlot::orientationChangedSlot(QAction* action) {
0333     if (action == orientationHorizontalAction)
0334         this->setOrientation(Axis::Orientation::Horizontal);
0335     else
0336         this->setOrientation(Axis::Orientation::Vertical);
0337 }
0338 
0339 // ##############################################################################
0340 // ####################### Private implementation ###############################
0341 // ##############################################################################
0342 BarPlotPrivate::BarPlotPrivate(BarPlot* owner)
0343     : PlotPrivate(owner)
0344     , q(owner) {
0345     setFlag(QGraphicsItem::ItemIsSelectable);
0346     setAcceptHoverEvents(false);
0347 }
0348 
0349 Background* BarPlotPrivate::addBackground(const KConfigGroup& group) {
0350     auto* background = new Background(QString());
0351     background->setPrefix(QLatin1String("Filling"));
0352     background->setEnabledAvailable(true);
0353     background->setHidden(true);
0354     q->addChild(background);
0355 
0356     if (!q->isLoading())
0357         background->init(group);
0358 
0359     q->connect(background, &Background::updateRequested, [=] {
0360         updatePixmap();
0361         Q_EMIT q->appearanceChanged();
0362     });
0363 
0364     backgrounds << background;
0365 
0366     return background;
0367 }
0368 
0369 Line* BarPlotPrivate::addBorderLine(const KConfigGroup& group) {
0370     auto* line = new Line(QString());
0371     line->setPrefix(QLatin1String("Border"));
0372     line->setHidden(true);
0373     q->addChild(line);
0374     if (!q->isLoading())
0375         line->init(group);
0376 
0377     q->connect(line, &Line::updatePixmapRequested, [=] {
0378         updatePixmap();
0379         Q_EMIT q->appearanceChanged();
0380     });
0381 
0382     q->connect(line, &Line::updateRequested, [=] {
0383         recalcShapeAndBoundingRect();
0384         Q_EMIT q->appearanceChanged();
0385     });
0386 
0387     borderLines << line;
0388 
0389     return line;
0390 }
0391 
0392 void BarPlotPrivate::addValue(const KConfigGroup& group) {
0393     value = new Value(QString());
0394     q->addChild(value);
0395     value->setHidden(true);
0396     value->setcenterPositionAvailable(true);
0397     if (!q->isLoading())
0398         value->init(group);
0399 
0400     q->connect(value, &Value::updatePixmapRequested, [=] {
0401         updatePixmap();
0402     });
0403 
0404     q->connect(value, &Value::updateRequested, [=] {
0405         updateValues();
0406     });
0407 }
0408 
0409 /*!
0410   called when the size of the plot or its data ranges (manual changes, zooming, etc.) were changed.
0411   recalculates the position of the scene points to be drawn.
0412   triggers the update of lines, drop lines, symbols etc.
0413 */
0414 void BarPlotPrivate::retransform() {
0415     const bool suppressed = suppressRetransform || !isVisible() || q->isLoading();
0416     Q_EMIT trackRetransformCalled(suppressed);
0417     if (suppressed)
0418         return;
0419 
0420     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0421 
0422     const int count = dataColumns.size();
0423     if (!count || m_barLines.size() != count) {
0424         // no columns or recalc() was not called yet, nothing to do
0425         recalcShapeAndBoundingRect();
0426         return;
0427     }
0428 
0429     m_stackedBarPositiveOffsets.fill(0);
0430     m_stackedBarNegativeOffsets.fill(0);
0431 
0432     m_valuesPointsLogical.clear();
0433 
0434     if (count) {
0435         if (orientation == BarPlot::Orientation::Vertical) {
0436             for (int i = 0; i < count; ++i) {
0437                 if (dataColumns.at(i))
0438                     verticalBarPlot(i);
0439             }
0440         } else {
0441             for (int i = 0; i < count; ++i) {
0442                 if (dataColumns.at(i))
0443                     horizontalBarPlot(i);
0444             }
0445         }
0446     }
0447 
0448     updateValues(); // this also calls recalcShapeAndBoundingRect()
0449 }
0450 
0451 /*!
0452  * called when the data columns or their values were changed
0453  * calculates the min and max values for x and y and calls dataChanged()
0454  * to trigger the retransform in the parent plot
0455  */
0456 void BarPlotPrivate::recalc() {
0457     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0458 
0459     const int newSize = dataColumns.size();
0460     // resize the internal containers
0461     m_barLines.clear();
0462     m_barLines.resize(newSize);
0463     m_fillPolygons.clear();
0464     m_fillPolygons.resize(newSize);
0465 
0466     const double xMinOld = xMin;
0467     const double xMaxOld = xMax;
0468     const double yMinOld = yMin;
0469     const double yMaxOld = yMax;
0470 
0471     // bar properties
0472     int diff = newSize - backgrounds.size();
0473     if (diff > 0) {
0474         // one more bar needs to be added
0475         KConfig config;
0476         KConfigGroup group = config.group(QLatin1String("BarPlot"));
0477         const auto* plot = static_cast<const CartesianPlot*>(q->parentAspect());
0478 
0479         for (int i = 0; i < diff; ++i) {
0480             // box filling and border line
0481             auto* background = addBackground(group);
0482             auto* line = addBorderLine(group);
0483 
0484             if (plot) {
0485                 const auto& themeColor = plot->themeColorPalette(backgrounds.count() - 1);
0486                 background->setFirstColor(themeColor);
0487                 line->setColor(themeColor);
0488             }
0489         }
0490     } else if (diff < 0) {
0491         // the last bar was deleted
0492         //      if (newSize != 0) {
0493         //          delete backgrounds.takeLast();
0494         //      }
0495     }
0496 
0497     // determine the number of bar groups that we need to draw.
0498     // this number is equal to the max number of non-empty
0499     // values in the provided datasets
0500     int barGroupsCount = 0;
0501     int columnIndex = 0;
0502     for (auto* column : qAsConst(dataColumns)) {
0503         int size = static_cast<const Column*>(column)->statistics().size;
0504         m_barLines[columnIndex].resize(size);
0505         m_fillPolygons[columnIndex].resize(size);
0506         if (size > barGroupsCount)
0507             barGroupsCount = size;
0508 
0509         ++columnIndex;
0510     }
0511 
0512     m_stackedBarPositiveOffsets.resize(barGroupsCount);
0513     m_stackedBarNegativeOffsets.resize(barGroupsCount);
0514 
0515     m_stackedBar100PercentValues.resize(barGroupsCount);
0516     m_stackedBar100PercentValues.fill(0);
0517 
0518     // if an x-column was provided and it has less values than the count determined
0519     // above, we limit the number of bars to the number of values in the x-column
0520     if (xColumn) {
0521         int size = static_cast<const Column*>(xColumn)->statistics().size;
0522         if (size < barGroupsCount)
0523             barGroupsCount = size;
0524     }
0525 
0526     // calculate the new min and max values of the bar plot
0527     QVector<double> barMins(barGroupsCount);
0528     QVector<double> barMaxs(barGroupsCount);
0529     if (type == BarPlot::Type::Stacked) {
0530         for (auto* column : dataColumns) {
0531             int valueIndex = 0;
0532             for (int i = 0; i < column->rowCount(); ++i) {
0533                 if (!column->isValid(i) || column->isMasked(i))
0534                     continue;
0535 
0536                 double value = column->valueAt(i);
0537                 if (value > 0)
0538                     barMaxs[valueIndex] += value;
0539                 if (value < 0)
0540                     barMins[valueIndex] += value;
0541 
0542                 ++valueIndex;
0543             }
0544         }
0545     } else if (type == BarPlot::Type::Stacked_100_Percent) {
0546         for (auto* column : dataColumns) {
0547             int valueIndex = 0;
0548             for (int i = 0; i < column->rowCount(); ++i) {
0549                 if (!column->isValid(i) || column->isMasked(i))
0550                     continue;
0551 
0552                 m_stackedBar100PercentValues[valueIndex] += column->valueAt(i);
0553                 ++valueIndex;
0554             }
0555         }
0556     }
0557 
0558     // determine min and max values for x- and y-ranges.
0559     // the first group is placed between 0 and 1, the second one between 1 and 2, etc.
0560     if (orientation == BarPlot::Orientation::Vertical) {
0561         // min/max for x
0562         if (xColumn) {
0563             xMin = xColumn->minimum() - 0.5;
0564             xMax = xColumn->maximum() + 0.5;
0565         } else {
0566             xMin = 0.0;
0567             xMax = barGroupsCount;
0568         }
0569 
0570         // min/max for y
0571         yMin = 0;
0572         yMax = -INFINITY;
0573         switch (type) {
0574         case BarPlot::Type::Grouped: {
0575             for (auto* column : dataColumns) {
0576                 double max = column->maximum();
0577                 if (max > yMax)
0578                     yMax = max;
0579 
0580                 double min = column->minimum();
0581                 if (min < yMin)
0582                     yMin = min;
0583             }
0584             break;
0585         }
0586         case BarPlot::Type::Stacked: {
0587             if (!barMaxs.isEmpty())
0588                 yMax = *std::max_element(barMaxs.constBegin(), barMaxs.constEnd());
0589             if (!barMins.isEmpty())
0590                 yMin = *std::min_element(barMins.constBegin(), barMins.constEnd());
0591             break;
0592         }
0593         case BarPlot::Type::Stacked_100_Percent: {
0594             yMax = 100;
0595         }
0596         }
0597 
0598         // if there are no negative values, we plot
0599         // in the positive y-direction only and we start at y=0
0600         if (yMin > 0)
0601             yMin = 0;
0602     } else { // horizontal
0603         // min/max for x
0604         xMin = 0;
0605         xMax = -INFINITY;
0606         switch (type) {
0607         case BarPlot::Type::Grouped: {
0608             for (auto* column : dataColumns) {
0609                 double max = column->maximum();
0610                 if (max > xMax)
0611                     xMax = max;
0612 
0613                 double min = column->minimum();
0614                 if (min < xMin)
0615                     xMin = min;
0616             }
0617             break;
0618         }
0619         case BarPlot::Type::Stacked: {
0620             xMax = *std::max_element(barMaxs.constBegin(), barMaxs.constEnd());
0621             xMin = *std::min_element(barMins.constBegin(), barMins.constEnd());
0622             break;
0623         }
0624         case BarPlot::Type::Stacked_100_Percent: {
0625             xMax = 100;
0626         }
0627         }
0628 
0629         // if there are no negative values, we plot
0630         // in the positive x-direction only and we start at x=0
0631         if (xMin > 0)
0632             xMin = 0;
0633 
0634         // min/max for y
0635         if (xColumn) {
0636             yMin = xColumn->minimum() - 0.5;
0637             yMax = xColumn->maximum() + 0.5;
0638         } else {
0639             yMin = 0.0;
0640             yMax = barGroupsCount;
0641         }
0642     }
0643 
0644     // determine the width of a group and of the gaps around a group
0645     m_groupWidth = 1.0;
0646     if (xColumn && newSize != 0)
0647         m_groupWidth = (xColumn->maximum() - xColumn->minimum()) / newSize;
0648 
0649     m_groupGap = m_groupWidth * 0.1; // gap around a group - the gap between two neighbour groups is 2*m_groupGap
0650 
0651     // if the size of the plot has changed because of the actual
0652     // data changes or because of new plot settings, emit dataChanged()
0653     // in order to recalculate the data ranges in the parent plot area
0654     // and to retransform all its children.
0655     // Just call retransform() to update the plot only if the ranges didn't change.
0656     if (xMin != xMinOld || xMax != xMaxOld || yMin != yMinOld || yMax != yMaxOld)
0657         Q_EMIT q->dataChanged();
0658     else
0659         retransform();
0660 }
0661 
0662 void BarPlotPrivate::verticalBarPlot(int columnIndex) {
0663     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0664 
0665     const auto* column = static_cast<const Column*>(dataColumns.at(columnIndex));
0666     QVector<QLineF> lines; // four lines for one bar in logical coordinates
0667     QVector<QVector<QLineF>> barLines; // lines for all bars for one colum in scene coordinates
0668 
0669     switch (type) {
0670     case BarPlot::Type::Grouped: {
0671         const double barGap = m_groupWidth * 0.1; // gap between two bars within a group
0672         const int barCount = dataColumns.size(); // number of bars within a group
0673         const double width = (m_groupWidth * widthFactor - 2 * m_groupGap - (barCount - 1) * barGap) / barCount; // bar width
0674 
0675         int valueIndex = 0;
0676         for (int i = 0; i < column->rowCount(); ++i) {
0677             if (!column->isValid(i) || column->isMasked(i))
0678                 continue;
0679 
0680             const double value = column->valueAt(i);
0681             double x;
0682 
0683             if (xColumn)
0684                 x = xColumn->valueAt(i);
0685             else
0686                 x = m_groupGap + m_groupWidth * (1 - widthFactor) / 2
0687                     + valueIndex * m_groupWidth; // translate to the beginning of the group - 1st group is placed between 0 and 1, 2nd between 1 and 2, etc.
0688 
0689             x += (width + barGap) * columnIndex; // translate to the beginning of the bar within the current group
0690 
0691             lines.clear();
0692             lines << QLineF(x, value, x + width, value);
0693             lines << QLineF(x + width, value, x + width, 0);
0694             lines << QLineF(x + width, 0, x, 0);
0695             lines << QLineF(x, 0, x, value);
0696 
0697             m_valuesPointsLogical << QPointF(x + width / 2, value);
0698 
0699             barLines << q->cSystem->mapLogicalToScene(lines);
0700             updateFillingRect(columnIndex, valueIndex, lines);
0701 
0702             ++valueIndex;
0703         }
0704         break;
0705     }
0706     case BarPlot::Type::Stacked: {
0707         const double width = m_groupWidth * widthFactor - 2 * m_groupGap; // bar width
0708         int valueIndex = 0;
0709         for (int i = 0; i < column->rowCount(); ++i) {
0710             if (!column->isValid(i) || column->isMasked(i))
0711                 continue;
0712 
0713             const double value = column->valueAt(i);
0714             double offset;
0715             if (value > 0)
0716                 offset = m_stackedBarPositiveOffsets.at(valueIndex);
0717             else
0718                 offset = m_stackedBarNegativeOffsets.at(valueIndex);
0719 
0720             double x;
0721             if (xColumn)
0722                 x = xColumn->valueAt(i);
0723             else
0724                 x = m_groupGap + m_groupWidth * (1 - widthFactor) / 2 + valueIndex * m_groupWidth; // translate to the beginning of the group
0725 
0726             lines.clear();
0727             lines << QLineF(x, value + offset, x + width, value + offset);
0728             lines << QLineF(x + width, value + offset, x + width, offset);
0729             lines << QLineF(x + width, offset, x, offset);
0730             lines << QLineF(x, offset, x, value + offset);
0731 
0732             if (value > 0) {
0733                 m_stackedBarPositiveOffsets[valueIndex] += value;
0734                 m_valuesPointsLogical << QPointF(x + width / 2, m_stackedBarPositiveOffsets.at(valueIndex));
0735             } else {
0736                 m_stackedBarNegativeOffsets[valueIndex] += value;
0737                 m_valuesPointsLogical << QPointF(x + width / 2, m_stackedBarNegativeOffsets.at(valueIndex));
0738             }
0739 
0740             barLines << q->cSystem->mapLogicalToScene(lines);
0741             updateFillingRect(columnIndex, valueIndex, lines);
0742 
0743             ++valueIndex;
0744         }
0745         break;
0746     }
0747     case BarPlot::Type::Stacked_100_Percent: {
0748         const double width = m_groupWidth * widthFactor - 2 * m_groupGap; // bar width
0749         int valueIndex = 0;
0750 
0751         for (int i = 0; i < column->rowCount(); ++i) {
0752             if (!column->isValid(i) || column->isMasked(i))
0753                 continue;
0754 
0755             double value = column->valueAt(i);
0756             if (value < 0)
0757                 continue;
0758 
0759             value = value * 100 / m_stackedBar100PercentValues.at(valueIndex);
0760             double offset = m_stackedBarPositiveOffsets.at(valueIndex);
0761             double x;
0762 
0763             if (xColumn)
0764                 x = xColumn->valueAt(i);
0765             else
0766                 x = m_groupGap + m_groupWidth * (1 - widthFactor) / 2 + valueIndex * m_groupWidth; // translate to the beginning of the group
0767 
0768             lines.clear();
0769             lines << QLineF(x, value + offset, x + width, value + offset);
0770             lines << QLineF(x + width, value + offset, x + width, offset);
0771             lines << QLineF(x + width, offset, x, offset);
0772             lines << QLineF(x, offset, x, value + offset);
0773 
0774             m_stackedBarPositiveOffsets[valueIndex] += value;
0775 
0776             m_valuesPointsLogical << QPointF(x + width / 2, m_stackedBarPositiveOffsets.at(valueIndex));
0777 
0778             barLines << q->cSystem->mapLogicalToScene(lines);
0779             updateFillingRect(columnIndex, valueIndex, lines);
0780 
0781             ++valueIndex;
0782         }
0783     }
0784     }
0785 
0786     m_barLines[columnIndex] = barLines;
0787 }
0788 
0789 void BarPlotPrivate::horizontalBarPlot(int columnIndex) {
0790     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0791 
0792     const auto* column = static_cast<const Column*>(dataColumns.at(columnIndex));
0793     QVector<QLineF> lines; // four lines for one bar in logical coordinates
0794     QVector<QVector<QLineF>> barLines; // lines for all bars for one colum in scene coordinates
0795 
0796     switch (type) {
0797     case BarPlot::Type::Grouped: {
0798         const double barGap = m_groupWidth * 0.1; // gap between two bars within a group
0799         const int barCount = dataColumns.size(); // number of bars within a group
0800         const double width = (m_groupWidth * widthFactor - 2 * m_groupGap - (barCount - 1) * barGap) / barCount; // bar width
0801 
0802         int valueIndex = 0;
0803         for (int i = 0; i < column->rowCount(); ++i) {
0804             if (!column->isValid(i) || column->isMasked(i))
0805                 continue;
0806 
0807             const double value = column->valueAt(i);
0808             double y;
0809             if (xColumn)
0810                 y = xColumn->valueAt(i);
0811             else
0812                 y = m_groupGap + m_groupWidth * (1 - widthFactor) / 2 + valueIndex * m_groupWidth; // translate to the beginning of the group
0813 
0814             y += (width + barGap) * columnIndex; // translate to the beginning of the bar within the current group
0815 
0816             lines.clear();
0817             lines << QLineF(value, y, value, y + width);
0818             lines << QLineF(value, y + width, 0, y + width);
0819             lines << QLineF(0, y + width, 0, y);
0820             lines << QLineF(0, y, value, y);
0821 
0822             m_valuesPointsLogical << QPointF(value, y + width / 2);
0823 
0824             barLines << q->cSystem->mapLogicalToScene(lines);
0825             updateFillingRect(columnIndex, valueIndex, lines);
0826 
0827             ++valueIndex;
0828         }
0829         break;
0830     }
0831     case BarPlot::Type::Stacked: {
0832         const double width = m_groupWidth * widthFactor - 2 * m_groupGap; // bar width
0833         int valueIndex = 0;
0834         for (int i = 0; i < column->rowCount(); ++i) {
0835             if (!column->isValid(i) || column->isMasked(i))
0836                 continue;
0837 
0838             const double value = column->valueAt(i);
0839             double offset;
0840             if (value > 0)
0841                 offset = m_stackedBarPositiveOffsets.at(valueIndex);
0842             else
0843                 offset = m_stackedBarNegativeOffsets.at(valueIndex);
0844 
0845             double y;
0846             if (xColumn)
0847                 y = xColumn->valueAt(i);
0848             else
0849                 y = m_groupGap + m_groupWidth * (1 - widthFactor) / 2 + valueIndex * m_groupWidth; // translate to the beginning of the group
0850 
0851             lines.clear();
0852             lines << QLineF(value + offset, y, value + offset, y + width);
0853             lines << QLineF(value + offset, y + width, offset, y + width);
0854             lines << QLineF(offset, y + width, offset, y);
0855             lines << QLineF(offset, y, value + offset, y);
0856 
0857             if (value > 0) {
0858                 m_stackedBarPositiveOffsets[valueIndex] += value;
0859                 m_valuesPointsLogical << QPointF(m_stackedBarPositiveOffsets.at(valueIndex), y + width / 2);
0860             } else {
0861                 m_stackedBarNegativeOffsets[valueIndex] += value;
0862                 m_valuesPointsLogical << QPointF(m_stackedBarNegativeOffsets.at(valueIndex), y + width / 2);
0863             }
0864             barLines << q->cSystem->mapLogicalToScene(lines);
0865             updateFillingRect(columnIndex, valueIndex, lines);
0866 
0867             ++valueIndex;
0868         }
0869         break;
0870     }
0871     case BarPlot::Type::Stacked_100_Percent: {
0872         const double width = m_groupWidth * widthFactor - 2 * m_groupGap; // bar width
0873         int valueIndex = 0;
0874         for (int i = 0; i < column->rowCount(); ++i) {
0875             if (!column->isValid(i) || column->isMasked(i))
0876                 continue;
0877 
0878             double value = column->valueAt(i);
0879             if (value < 0)
0880                 continue;
0881 
0882             value = value * 100 / m_stackedBar100PercentValues.at(valueIndex);
0883             double offset = m_stackedBarPositiveOffsets.at(valueIndex);
0884 
0885             double y;
0886             if (xColumn)
0887                 y = xColumn->valueAt(i);
0888             else
0889                 y = m_groupGap + m_groupWidth * (1 - widthFactor) / 2 + valueIndex * m_groupWidth; // translate to the beginning of the group
0890 
0891             lines.clear();
0892             lines << QLineF(value + offset, y, value + offset, y + width);
0893             lines << QLineF(value + offset, y + width, offset, y + width);
0894             lines << QLineF(offset, y + width, offset, y);
0895             lines << QLineF(offset, y, value + offset, y);
0896 
0897             m_stackedBarPositiveOffsets[valueIndex] += value;
0898             m_valuesPointsLogical << QPointF(m_stackedBarPositiveOffsets.at(valueIndex), y + width / 2);
0899 
0900             barLines << q->cSystem->mapLogicalToScene(lines);
0901             updateFillingRect(columnIndex, valueIndex, lines);
0902 
0903             ++valueIndex;
0904         }
0905     }
0906     }
0907 
0908     m_barLines[columnIndex] = barLines;
0909 }
0910 
0911 void BarPlotPrivate::updateFillingRect(int columnIndex, int valueIndex, const QVector<QLineF>& lines) {
0912     const auto& unclippedLines = q->cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
0913 
0914     if (unclippedLines.isEmpty()) {
0915         m_fillPolygons[columnIndex][valueIndex] = QPolygonF();
0916         return;
0917     }
0918 
0919     // we have four unclipped lines for the box.
0920     // clip the points to the plot data rect and create a new polygon
0921     // out of them that will be filled out.
0922     QPolygonF polygon;
0923     const QRectF& dataRect = static_cast<CartesianPlot*>(q->parentAspect())->dataRect();
0924     int i = 0;
0925     for (const auto& line : unclippedLines) {
0926         // clip the first point of the line
0927         QPointF p1 = line.p1();
0928         if (p1.x() < dataRect.left())
0929             p1.setX(dataRect.left());
0930         else if (p1.x() > dataRect.right())
0931             p1.setX(dataRect.right());
0932 
0933         if (p1.y() < dataRect.top())
0934             p1.setY(dataRect.top());
0935         else if (p1.y() > dataRect.bottom())
0936             p1.setY(dataRect.bottom());
0937 
0938         // clip the second point of the line
0939         QPointF p2 = line.p2();
0940         if (p2.x() < dataRect.left())
0941             p2.setX(dataRect.left());
0942         else if (p2.x() > dataRect.right())
0943             p2.setX(dataRect.right());
0944 
0945         if (p2.y() < dataRect.top())
0946             p2.setY(dataRect.top());
0947         else if (p2.y() > dataRect.bottom())
0948             p2.setY(dataRect.bottom());
0949 
0950         if (i != unclippedLines.size() - 1)
0951             polygon << p1;
0952         else {
0953             // close the polygon for the last line
0954             polygon << p1;
0955             polygon << p2;
0956         }
0957 
0958         ++i;
0959     }
0960 
0961     m_fillPolygons[columnIndex][valueIndex] = polygon;
0962 }
0963 
0964 void BarPlotPrivate::updateValues() {
0965     m_valuesPath = QPainterPath();
0966     m_valuesPoints.clear();
0967     m_valuesStrings.clear();
0968 
0969     if (value->type() == Value::NoValues) {
0970         recalcShapeAndBoundingRect();
0971         return;
0972     }
0973 
0974     // determine the value string for all points that are currently visible in the plot
0975     auto visiblePoints = std::vector<bool>(m_valuesPointsLogical.count(), false);
0976     Points pointsScene;
0977     q->cSystem->mapLogicalToScene(m_valuesPointsLogical, pointsScene, visiblePoints);
0978     const auto& prefix = value->prefix();
0979     const auto& suffix = value->suffix();
0980     if (value->type() == Value::BinEntries) {
0981         for (int i = 0; i < m_valuesPointsLogical.count(); ++i) {
0982             if (!visiblePoints[i])
0983                 continue;
0984 
0985             auto& point = m_valuesPointsLogical.at(i);
0986             if (orientation == BarPlot::Orientation::Vertical) {
0987                 if (type == BarPlot::Type::Stacked_100_Percent)
0988                     m_valuesStrings << prefix + QString::number(point.y(), value->numericFormat(), 1) + QLatin1String("%") + suffix;
0989                 else
0990                     m_valuesStrings << prefix + QString::number(point.y()) + suffix;
0991             } else {
0992                 if (type == BarPlot::Type::Stacked_100_Percent)
0993                     m_valuesStrings << prefix + QString::number(point.x(), value->numericFormat(), 1) + QLatin1String("%") + suffix;
0994                 else
0995                     m_valuesStrings << prefix + QString::number(point.x()) + suffix;
0996             }
0997         }
0998     } else if (value->type() == Value::CustomColumn) {
0999         const auto* valuesColumn = value->column();
1000         if (!valuesColumn) {
1001             recalcShapeAndBoundingRect();
1002             return;
1003         }
1004 
1005 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1006         const int endRow = std::min(m_valuesPointsLogical.size(), static_cast<qsizetype>(valuesColumn->rowCount()));
1007 #else
1008         const int endRow = std::min(m_valuesPointsLogical.size(), valuesColumn->rowCount());
1009 #endif
1010         const auto xColMode = valuesColumn->columnMode();
1011         for (int i = 0; i < endRow; ++i) {
1012             if (!valuesColumn->isValid(i) || valuesColumn->isMasked(i))
1013                 continue;
1014 
1015             switch (xColMode) {
1016             case AbstractColumn::ColumnMode::Double:
1017                 if (type == BarPlot::Type::Stacked_100_Percent)
1018                     m_valuesStrings << prefix + QString::number(valuesColumn->valueAt(i), value->numericFormat(), 1) + QString::fromStdString("%");
1019                 else
1020                     m_valuesStrings << prefix + QString::number(valuesColumn->valueAt(i), value->numericFormat(), value->precision()) + suffix;
1021                 break;
1022             case AbstractColumn::ColumnMode::Integer:
1023             case AbstractColumn::ColumnMode::BigInt:
1024                 m_valuesStrings << prefix + QString::number(valuesColumn->valueAt(i)) + suffix;
1025                 break;
1026             case AbstractColumn::ColumnMode::Text:
1027                 m_valuesStrings << prefix + valuesColumn->textAt(i) + suffix;
1028                 break;
1029             case AbstractColumn::ColumnMode::DateTime:
1030             case AbstractColumn::ColumnMode::Month:
1031             case AbstractColumn::ColumnMode::Day:
1032                 m_valuesStrings << prefix + valuesColumn->dateTimeAt(i).toString(value->dateTimeFormat()) + suffix;
1033                 break;
1034             }
1035         }
1036     }
1037 
1038     // Calculate the coordinates where to paint the value strings.
1039     // The coordinates depend on the actual size of the string.
1040     QFontMetrics fm(value->font());
1041     qreal w;
1042     const qreal h = fm.ascent();
1043     int offset = value->distance();
1044     switch (value->position()) {
1045     case Value::Above:
1046         for (int i = 0; i < m_valuesStrings.size(); i++) {
1047             w = fm.boundingRect(m_valuesStrings.at(i)).width();
1048             const auto& point = pointsScene.at(i);
1049 
1050             if (orientation == BarPlot::Orientation::Vertical)
1051                 m_valuesPoints << QPointF(point.x() - w / 2, point.y() - offset);
1052             else
1053                 m_valuesPoints << QPointF(point.x(), point.y() - offset);
1054         }
1055         break;
1056     case Value::Center: {
1057         QVector<qreal> listBarWidth;
1058         for (int i = 0, j = 0; i < m_barLines.size(); i++) {
1059             auto& columnBarLines = m_barLines.at(i);
1060 
1061             for (int i = 0; i < columnBarLines.size(); i++, j++) { // loop over the different data columns
1062                 if (visiblePoints.at(j) == true)
1063                     listBarWidth.append(columnBarLines.at(i).at(1).length());
1064             }
1065         }
1066         for (int i = 0; i < m_valuesStrings.size(); i++) {
1067             w = fm.boundingRect(m_valuesStrings.at(i)).width();
1068             const auto& point = pointsScene.at(i);
1069             if (orientation == BarPlot::Orientation::Vertical)
1070                 m_valuesPoints << QPointF(point.x() - w / 2,
1071                                           point.y() + listBarWidth.at(i) / 2 + offset - Worksheet::convertToSceneUnits(1, Worksheet::Unit::Point));
1072             else
1073                 m_valuesPoints << QPointF(point.x() - listBarWidth.at(i) / 2 - offset + h / 2 - w / 2, point.y() + h / 2);
1074         }
1075         break;
1076     }
1077     case Value::Under:
1078         for (int i = 0; i < m_valuesStrings.size(); i++) {
1079             w = fm.boundingRect(m_valuesStrings.at(i)).width();
1080             const auto& point = pointsScene.at(i);
1081             if (orientation == BarPlot::Orientation::Vertical)
1082                 m_valuesPoints << QPointF(point.x() - w / 2, point.y() + offset + h / 2);
1083             else
1084                 m_valuesPoints << QPointF(point.x(), point.y() + offset + h / 2);
1085         }
1086         break;
1087     case Value::Left:
1088         for (int i = 0; i < m_valuesStrings.size(); i++) {
1089             w = fm.boundingRect(m_valuesStrings.at(i)).width();
1090             const auto& point = pointsScene.at(i);
1091             if (orientation == BarPlot::Orientation::Vertical)
1092                 m_valuesPoints << QPointF(point.x() - offset - w, point.y());
1093             else
1094                 m_valuesPoints << QPointF(point.x() - offset - w, point.y() + h / 2);
1095         }
1096         break;
1097     case Value::Right:
1098         for (int i = 0; i < m_valuesStrings.size(); i++) {
1099             w = fm.boundingRect(m_valuesStrings.at(i)).width();
1100             const auto& point = pointsScene.at(i);
1101             if (orientation == BarPlot::Orientation::Vertical)
1102                 m_valuesPoints << QPointF(point.x() + offset, point.y());
1103             else
1104                 m_valuesPoints << QPointF(point.x() + offset, point.y() + h / 2);
1105         }
1106         break;
1107     }
1108 
1109     QTransform trafo;
1110     QPainterPath path;
1111     const double angle = value->rotationAngle();
1112     for (int i = 0; i < m_valuesPoints.size(); i++) {
1113         path = QPainterPath();
1114         path.addText(QPoint(0, 0), value->font(), m_valuesStrings.at(i));
1115 
1116         trafo.reset();
1117         trafo.translate(m_valuesPoints.at(i).x(), m_valuesPoints.at(i).y());
1118         if (angle != 0.)
1119             trafo.rotate(-angle);
1120 
1121         m_valuesPath.addPath(trafo.map(path));
1122     }
1123 
1124     recalcShapeAndBoundingRect();
1125 }
1126 
1127 /*!
1128   recalculates the outer bounds and the shape of the item.
1129 */
1130 void BarPlotPrivate::recalcShapeAndBoundingRect() {
1131     prepareGeometryChange();
1132     m_shape = QPainterPath();
1133 
1134     int index = 0;
1135     for (const auto& columnBarLines : m_barLines) { // loop over the different data columns
1136         for (const auto& barLines : columnBarLines) { // loop over the bars for every data column
1137             QPainterPath barPath;
1138             for (const auto& line : barLines) { // loop over the four lines for every bar
1139                 barPath.moveTo(line.p1());
1140                 barPath.lineTo(line.p2());
1141             }
1142 
1143             if (index < borderLines.count()) { // TODO
1144                 const auto& borderPen = borderLines.at(index)->pen();
1145                 m_shape.addPath(WorksheetElement::shapeFromPath(barPath, borderPen));
1146             }
1147         }
1148         ++index;
1149     }
1150 
1151     if (value->type() != Value::NoValues)
1152         m_shape.addPath(m_valuesPath);
1153 
1154     m_boundingRectangle = m_shape.boundingRect();
1155     updatePixmap();
1156 }
1157 
1158 void BarPlotPrivate::updatePixmap() {
1159     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
1160     QPixmap pixmap(m_boundingRectangle.width(), m_boundingRectangle.height());
1161     if (m_boundingRectangle.width() == 0. || m_boundingRectangle.height() == 0.) {
1162         m_pixmap = pixmap;
1163         m_hoverEffectImageIsDirty = true;
1164         m_selectionEffectImageIsDirty = true;
1165         return;
1166     }
1167     pixmap.fill(Qt::transparent);
1168     QPainter painter(&pixmap);
1169     painter.setRenderHint(QPainter::Antialiasing, true);
1170     painter.translate(-m_boundingRectangle.topLeft());
1171 
1172     draw(&painter);
1173     painter.end();
1174 
1175     m_pixmap = pixmap;
1176     m_hoverEffectImageIsDirty = true;
1177     m_selectionEffectImageIsDirty = true;
1178     Q_EMIT q->changed();
1179     update();
1180 }
1181 
1182 void BarPlotPrivate::draw(QPainter* painter) {
1183     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
1184 
1185     int columnIndex = 0;
1186     for (const auto& columnBarLines : m_barLines) { // loop over the different data columns
1187         int valueIndex = 0;
1188         for (const auto& barLines : columnBarLines) { // loop over the bars for every data column
1189             // draw the box filling
1190             if (columnIndex < backgrounds.size()) { // TODO: remove this check later
1191                 const auto* background = backgrounds.at(columnIndex);
1192                 if (background->enabled()) {
1193                     painter->setOpacity(background->opacity());
1194                     painter->setPen(Qt::NoPen);
1195                     const QPolygonF& polygon = m_fillPolygons.at(columnIndex).at(valueIndex);
1196                     drawFillingPollygon(polygon, painter, background);
1197                 }
1198             }
1199 
1200             // draw the border
1201             if (columnIndex < borderLines.size()) { // TODO: remove this check later
1202                 const auto& borderPen = borderLines.at(columnIndex)->pen();
1203                 const double borderOpacity = borderLines.at(columnIndex)->opacity();
1204                 if (borderPen.style() != Qt::NoPen) {
1205                     painter->setPen(borderPen);
1206                     painter->setBrush(Qt::NoBrush);
1207                     painter->setOpacity(borderOpacity);
1208                     for (const auto& line : barLines) // loop over the four lines for every bar
1209                         painter->drawLine(line);
1210                 }
1211             }
1212 
1213             ++valueIndex;
1214         }
1215         ++columnIndex;
1216     }
1217 
1218     // draw values
1219     value->draw(painter, m_valuesPoints, m_valuesStrings);
1220 }
1221 
1222 void BarPlotPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) {
1223     if (!isVisible())
1224         return;
1225 
1226     painter->setPen(Qt::NoPen);
1227     painter->setBrush(Qt::NoBrush);
1228     painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
1229 
1230     if (Settings::group(QStringLiteral("Settings_Worksheet")).readEntry<bool>("DoubleBuffering", true))
1231         painter->drawPixmap(m_boundingRectangle.topLeft(), m_pixmap); // draw the cached pixmap (fast)
1232     else
1233         draw(painter); // draw directly again (slow)
1234 
1235     if (m_hovered && !isSelected() && !q->isPrinting()) {
1236         if (m_hoverEffectImageIsDirty) {
1237             QPixmap pix = m_pixmap;
1238             QPainter p(&pix);
1239             p.setCompositionMode(QPainter::CompositionMode_SourceIn); // source (shadow) pixels merged with the alpha channel of the destination (m_pixmap)
1240             p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Shadow));
1241             p.end();
1242 
1243             m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5);
1244             m_hoverEffectImageIsDirty = false;
1245         }
1246 
1247         painter->drawImage(m_boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect());
1248         return;
1249     }
1250 
1251     if (isSelected() && !q->isPrinting()) {
1252         if (m_selectionEffectImageIsDirty) {
1253             QPixmap pix = m_pixmap;
1254             QPainter p(&pix);
1255             p.setCompositionMode(QPainter::CompositionMode_SourceIn);
1256             p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Highlight));
1257             p.end();
1258 
1259             m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5);
1260             m_selectionEffectImageIsDirty = false;
1261         }
1262 
1263         painter->drawImage(m_boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect());
1264         return;
1265     }
1266 }
1267 
1268 // ##############################################################################
1269 // ##################  Serialization/Deserialization  ###########################
1270 // ##############################################################################
1271 //! Save as XML
1272 void BarPlot::save(QXmlStreamWriter* writer) const {
1273     Q_D(const BarPlot);
1274 
1275     writer->writeStartElement(QStringLiteral("barPlot"));
1276     writeBasicAttributes(writer);
1277     writeCommentElement(writer);
1278 
1279     // general
1280     writer->writeStartElement(QStringLiteral("general"));
1281     writer->writeAttribute(QStringLiteral("type"), QString::number(static_cast<int>(d->type)));
1282     writer->writeAttribute(QStringLiteral("orientation"), QString::number(static_cast<int>(d->orientation)));
1283     writer->writeAttribute(QStringLiteral("widthFactor"), QString::number(d->widthFactor));
1284     writer->writeAttribute(QStringLiteral("plotRangeIndex"), QString::number(m_cSystemIndex));
1285     writer->writeAttribute(QStringLiteral("xMin"), QString::number(d->xMin));
1286     writer->writeAttribute(QStringLiteral("xMax"), QString::number(d->xMax));
1287     writer->writeAttribute(QStringLiteral("yMin"), QString::number(d->yMin));
1288     writer->writeAttribute(QStringLiteral("yMax"), QString::number(d->yMax));
1289     writer->writeAttribute(QStringLiteral("visible"), QString::number(d->isVisible()));
1290     writer->writeAttribute(QStringLiteral("legendVisible"), QString::number(d->legendVisible));
1291 
1292     if (d->xColumn)
1293         writer->writeAttribute(QStringLiteral("xColumn"), d->xColumn->path());
1294 
1295     for (auto* column : d->dataColumns) {
1296         writer->writeStartElement(QStringLiteral("column"));
1297         writer->writeAttribute(QStringLiteral("path"), column->path());
1298         writer->writeEndElement();
1299     }
1300     writer->writeEndElement();
1301 
1302     // box filling
1303     for (auto* background : d->backgrounds)
1304         background->save(writer);
1305 
1306     // box border lines
1307     for (auto* line : d->borderLines)
1308         line->save(writer);
1309 
1310     // Values
1311     d->value->save(writer);
1312 
1313     writer->writeEndElement(); // close "BarPlot" section
1314 }
1315 
1316 //! Load from XML
1317 bool BarPlot::load(XmlStreamReader* reader, bool preview) {
1318     Q_D(BarPlot);
1319 
1320     if (!readBasicAttributes(reader))
1321         return false;
1322 
1323     QXmlStreamAttributes attribs;
1324     QString str;
1325     bool firstBackgroundRead = false;
1326     bool firstBorderLineRead = false;
1327 
1328     while (!reader->atEnd()) {
1329         reader->readNext();
1330         if (reader->isEndElement() && reader->name() == QLatin1String("barPlot"))
1331             break;
1332 
1333         if (!reader->isStartElement())
1334             continue;
1335 
1336         if (!preview && reader->name() == QLatin1String("comment")) {
1337             if (!readCommentElement(reader))
1338                 return false;
1339         } else if (!preview && reader->name() == QLatin1String("general")) {
1340             attribs = reader->attributes();
1341 
1342             READ_INT_VALUE("type", type, BarPlot::Type);
1343             READ_INT_VALUE("orientation", orientation, BarPlot::Orientation);
1344             READ_DOUBLE_VALUE("widthFactor", widthFactor);
1345             READ_INT_VALUE_DIRECT("plotRangeIndex", m_cSystemIndex, int);
1346 
1347             READ_DOUBLE_VALUE("xMin", xMin);
1348             READ_DOUBLE_VALUE("xMax", xMax);
1349             READ_DOUBLE_VALUE("yMin", yMin);
1350             READ_DOUBLE_VALUE("yMax", yMax);
1351             READ_COLUMN(xColumn);
1352             READ_INT_VALUE("legendVisible", legendVisible, bool);
1353 
1354             str = attribs.value(QStringLiteral("visible")).toString();
1355             if (str.isEmpty())
1356                 reader->raiseMissingAttributeWarning(QStringLiteral("visible"));
1357             else
1358                 d->setVisible(str.toInt());
1359         } else if (reader->name() == QLatin1String("column")) {
1360             attribs = reader->attributes();
1361 
1362             str = attribs.value(QStringLiteral("path")).toString();
1363             if (!str.isEmpty())
1364                 d->dataColumnPaths << str;
1365             //          READ_COLUMN(dataColumn);
1366         } else if (!preview && reader->name() == QLatin1String("filling")) {
1367             if (!firstBackgroundRead) {
1368                 auto* background = d->backgrounds.at(0);
1369                 background->load(reader, preview);
1370                 firstBackgroundRead = true;
1371             } else {
1372                 auto* background = d->addBackground(KConfigGroup());
1373                 background->load(reader, preview);
1374             }
1375         } else if (!preview && reader->name() == QLatin1String("border")) {
1376             if (!firstBorderLineRead) {
1377                 auto* line = d->borderLines.at(0);
1378                 line->load(reader, preview);
1379                 firstBorderLineRead = true;
1380             } else {
1381                 auto* line = d->addBorderLine(KConfigGroup());
1382                 line->load(reader, preview);
1383             }
1384         } else if (!preview && reader->name() == QLatin1String("values")) {
1385             d->value->load(reader, preview);
1386         } else { // unknown element
1387             reader->raiseUnknownElementWarning();
1388             if (!reader->skipToEndElement())
1389                 return false;
1390         }
1391     }
1392 
1393     d->dataColumns.resize(d->dataColumnPaths.size());
1394 
1395     return true;
1396 }
1397 
1398 // ##############################################################################
1399 // #########################  Theme management ##################################
1400 // ##############################################################################
1401 void BarPlot::loadThemeConfig(const KConfig& config) {
1402     KConfigGroup group;
1403     if (config.hasGroup(QStringLiteral("Theme")))
1404         group = config.group(QStringLiteral("XYCurve")); // when loading from the theme config, use the same properties as for XYCurve
1405     else
1406         group = config.group(QStringLiteral("BarPlot"));
1407 
1408     const auto* plot = static_cast<const CartesianPlot*>(parentAspect());
1409     int index = plot->curveChildIndex(this);
1410     const QColor themeColor = plot->themeColorPalette(index);
1411 
1412     Q_D(BarPlot);
1413     d->suppressRecalc = true;
1414 
1415     // box filling
1416     for (int i = 0; i < d->backgrounds.count(); ++i) {
1417         auto* background = d->backgrounds.at(i);
1418         background->loadThemeConfig(group, plot->themeColorPalette(i));
1419     }
1420 
1421     // box border lines
1422     for (int i = 0; i < d->borderLines.count(); ++i) {
1423         auto* line = d->borderLines.at(i);
1424         line->loadThemeConfig(group, plot->themeColorPalette(i));
1425     }
1426 
1427     // Values
1428     d->value->loadThemeConfig(group, themeColor);
1429 
1430     d->suppressRecalc = false;
1431     d->recalcShapeAndBoundingRect();
1432 }