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 }