File indexing completed on 2024-09-22 04:37:26
0001 /* 0002 SPDX-FileCopyrightText: 2005 Ace Jones <acejones@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2005-2018 Thomas Baumgart <tbaumgart@kde.org> 0004 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0005 SPDX-FileCopyrightText: 2018 Michael Kiefer <mlkiefer@users.noreply.github.com> 0006 SPDX-FileCopyrightText: 2020 Robert Szczesiak <dev.rszczesiak@gmail.com> 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "kreportchartview.h" 0011 0012 // ---------------------------------------------------------------------------- 0013 // QT Includes 0014 #include <QStandardItemModel> 0015 #include <QFontDatabase> 0016 #include <QtMath> 0017 0018 // ---------------------------------------------------------------------------- 0019 // KDE Includes 0020 #include <KColorScheme> 0021 #include <KLocalizedString> 0022 0023 // ---------------------------------------------------------------------------- 0024 // Project Includes 0025 #include <KChartBackgroundAttributes> 0026 #include <KChartDataValueAttributes> 0027 #include <KChartMarkerAttributes> 0028 #include <KChartGridAttributes> 0029 #include <KChartHeaderFooter> 0030 #include <KChartLegend> 0031 #include <KChartLineDiagram> 0032 #include <KChartBarDiagram> 0033 #include <KChartPieDiagram> 0034 #include <KChartRingDiagram> 0035 #include <KChartCartesianAxis> 0036 #include <KChartFrameAttributes> 0037 #include "kmymoneysettings.h" 0038 #include "kreportcartesianaxis.h" 0039 #include "mymoneyfile.h" 0040 #include "mymoneysecurity.h" 0041 #include "mymoneyenums.h" 0042 0043 using namespace reports; 0044 0045 KReportChartView::KReportChartView(QWidget* parent) : 0046 KChart::Chart(parent), 0047 m_accountSeries(0), 0048 m_seriesTotals(0), 0049 m_numColumns(0), 0050 m_skipZero(0), 0051 m_backgroundBrush(KColorScheme(QPalette::Current).background()), 0052 m_foregroundBrush(KColorScheme(QPalette::Current).foreground()), 0053 m_precision(2) 0054 { 0055 // ******************************************************************** 0056 // Set KMyMoney's Chart Parameter Defaults 0057 // ******************************************************************** 0058 0059 //Set the background obtained from the color scheme 0060 BackgroundAttributes backAttr(backgroundAttributes()); 0061 backAttr.setBrush(m_backgroundBrush); 0062 backAttr.setVisible(true); 0063 setBackgroundAttributes(backAttr); 0064 } 0065 0066 void KReportChartView::drawPivotChart(const PivotGrid &grid, const MyMoneyReport &config, int numberColumns, const QStringList& columnHeadings, const QList<ERowType>& rowTypeList, const QStringList& columnTypeHeaderList) 0067 { 0068 if (numberColumns == 0) 0069 return; 0070 //set the number of columns 0071 setNumColumns(numberColumns); 0072 0073 //set skipZero 0074 m_skipZero = config.isSkippingZero(); 0075 0076 //remove existing headers 0077 const HeaderFooterList hfList = headerFooters(); 0078 for (const auto& hf : hfList) 0079 delete hf; 0080 0081 //remove existing legends 0082 const LegendList lgList = legends(); 0083 for (const auto& lg : lgList) 0084 delete lg; 0085 0086 //make sure the model is clear 0087 m_model.clear(); 0088 const bool blocked = m_model.blockSignals(true); // don't Q_EMIT dataChanged() signal during each drawPivotRowSet 0089 0090 //set the new header 0091 HeaderFooter* header = new HeaderFooter; 0092 // adding some extra spaces at the end here avoids truncation 0093 // of the label on the diagram and solves bug #399260 0094 header->setText(config.name().append(QLatin1String(" "))); 0095 header->setType(HeaderFooter::Header); 0096 header->setPosition(Position::North); 0097 TextAttributes headerTextAttr(header->textAttributes()); 0098 headerTextAttr.setPen(m_foregroundBrush.color()); 0099 header->setTextAttributes(headerTextAttr); 0100 addHeaderFooter(header); 0101 0102 // whether to limit the chart to use series totals only. Used for reports which only 0103 // show one dimension (pie). 0104 setSeriesTotals(false); 0105 0106 // whether series (rows) are accounts (true) or months (false). This causes a lot 0107 // of complexity in the charts. The problem is that circular reports work best with 0108 // an account in a COLUMN, while line/bar prefer it in a ROW. 0109 setAccountSeries(true); 0110 0111 switch (config.chartType()) { 0112 case eMyMoney::Report::ChartType::Line: 0113 case eMyMoney::Report::ChartType::Bar: 0114 case eMyMoney::Report::ChartType::StackedBar: { 0115 CartesianCoordinatePlane* cartesianPlane = new CartesianCoordinatePlane(this); 0116 cartesianPlane->setAutoAdjustVerticalRangeToData(2); 0117 cartesianPlane->setRubberBandZoomingEnabled(true); 0118 replaceCoordinatePlane(cartesianPlane); 0119 0120 // set-up axis type 0121 if (config.isLogYAxis()) 0122 cartesianPlane->setAxesCalcModeY(KChart::AbstractCoordinatePlane::Logarithmic); 0123 else 0124 cartesianPlane->setAxesCalcModeY(KChart::AbstractCoordinatePlane::Linear); 0125 0126 QLocale loc = locale(); 0127 // set-up grid 0128 GridAttributes ga = cartesianPlane->gridAttributes(Qt::Vertical); 0129 ga.setGridVisible(config.isChartCHGridLines()); 0130 ga.setGridStepWidth(config.isDataUserDefined() ? loc.toDouble(config.dataMajorTick()) : 0.0); 0131 ga.setGridSubStepWidth(config.isDataUserDefined() ? loc.toDouble(config.dataMinorTick()) : 0.0); 0132 cartesianPlane->setGridAttributes(Qt::Vertical, ga); 0133 0134 ga = cartesianPlane->gridAttributes(Qt::Horizontal); 0135 ga.setGridVisible(config.isChartSVGridLines()); 0136 cartesianPlane->setGridAttributes(Qt::Horizontal, ga); 0137 0138 // set-up data range 0139 cartesianPlane->setVerticalRange(qMakePair(config.isDataUserDefined() ? loc.toDouble(config.dataRangeStart()) : 0.0, 0140 config.isDataUserDefined() ? loc.toDouble(config.dataRangeEnd()) : 0.0)); 0141 0142 //set-up x axis 0143 CartesianAxis *xAxis = new CartesianAxis(); 0144 xAxis->setPosition(CartesianAxis::Bottom); 0145 xAxis->setTitleText(i18n("Time")); 0146 TextAttributes xAxisTitleTextAttr(xAxis->titleTextAttributes()); 0147 xAxisTitleTextAttr.setMinimalFontSize(QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize()); 0148 xAxisTitleTextAttr.setPen(m_foregroundBrush.color()); 0149 xAxis->setTitleTextAttributes(xAxisTitleTextAttr); 0150 TextAttributes xAxisTextAttr(xAxis->textAttributes()); 0151 xAxisTextAttr.setPen(m_foregroundBrush.color()); 0152 xAxis->setTextAttributes(xAxisTextAttr); 0153 RulerAttributes xAxisRulerAttr(xAxis->rulerAttributes()); 0154 xAxisRulerAttr.setTickMarkPen(m_foregroundBrush.color()); 0155 xAxisRulerAttr.setShowRulerLine(true); 0156 xAxis->setRulerAttributes(xAxisRulerAttr); 0157 0158 //set-up y axis 0159 KReportCartesianAxis *yAxis = new KReportCartesianAxis(loc, config.yLabelsPrecision()); 0160 yAxis->setPosition(CartesianAxis::Left); 0161 0162 if (config.isIncludingPrice()) 0163 yAxis->setTitleText(i18n("Price")); 0164 else 0165 yAxis->setTitleText(i18n("Balance")); 0166 0167 TextAttributes yAxisTitleTextAttr(yAxis->titleTextAttributes()); 0168 yAxisTitleTextAttr.setMinimalFontSize(QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize()); 0169 yAxisTitleTextAttr.setPen(m_foregroundBrush.color()); 0170 yAxis->setTitleTextAttributes(yAxisTitleTextAttr); 0171 TextAttributes yAxisTextAttr(yAxis->textAttributes()); 0172 yAxisTextAttr.setPen(m_foregroundBrush.color()); 0173 yAxis->setTextAttributes(yAxisTextAttr); 0174 RulerAttributes yAxisRulerAttr(yAxis->rulerAttributes()); 0175 yAxisRulerAttr.setTickMarkPen(m_foregroundBrush.color()); 0176 yAxisRulerAttr.setShowRulerLine(true); 0177 yAxis->setRulerAttributes(yAxisRulerAttr); 0178 0179 switch (config.chartType()) { 0180 case eMyMoney::Report::ChartType::End: 0181 case eMyMoney::Report::ChartType::Line: { 0182 KChart::LineDiagram* diagram = new KChart::LineDiagram(this, cartesianPlane); 0183 0184 if (config.isSkippingZero()) { 0185 LineAttributes attributes = diagram->lineAttributes(); 0186 attributes.setMissingValuesPolicy(LineAttributes::MissingValuesAreBridged); 0187 diagram->setLineAttributes(attributes); 0188 } 0189 cartesianPlane->replaceDiagram(diagram); 0190 diagram->addAxis(xAxis); 0191 diagram->addAxis(yAxis); 0192 break; 0193 } 0194 case eMyMoney::Report::ChartType::Bar: { 0195 KChart::BarDiagram* diagram = new KChart::BarDiagram(this, cartesianPlane); 0196 cartesianPlane->replaceDiagram(diagram); 0197 diagram->addAxis(xAxis); 0198 diagram->addAxis(yAxis); 0199 break; 0200 } 0201 case eMyMoney::Report::ChartType::StackedBar: { 0202 KChart::BarDiagram* diagram = new KChart::BarDiagram(this, cartesianPlane); 0203 diagram->setType(BarDiagram::Stacked); 0204 cartesianPlane->replaceDiagram(diagram); 0205 diagram->addAxis(xAxis); 0206 diagram->addAxis(yAxis); 0207 break; 0208 } 0209 default: 0210 break; 0211 } 0212 break; 0213 } 0214 case eMyMoney::Report::ChartType::Pie: 0215 case eMyMoney::Report::ChartType::Ring: { 0216 PolarCoordinatePlane* polarPlane = new PolarCoordinatePlane(this); 0217 replaceCoordinatePlane(polarPlane); 0218 0219 // set-up grid 0220 GridAttributes ga = polarPlane->gridAttributes(true); 0221 ga.setGridVisible(config.isChartCHGridLines()); 0222 polarPlane->setGridAttributes(true, ga); 0223 0224 ga = polarPlane->gridAttributes(false); 0225 ga.setGridVisible(config.isChartSVGridLines()); 0226 polarPlane->setGridAttributes(false, ga); 0227 0228 setAccountSeries(false); 0229 0230 switch (config.chartType()) { 0231 case eMyMoney::Report::ChartType::Pie: { 0232 KChart::PieDiagram* diagram = new KChart::PieDiagram(this, polarPlane); 0233 polarPlane->replaceDiagram(diagram); 0234 setSeriesTotals(true); 0235 break; 0236 } 0237 case eMyMoney::Report::ChartType::Ring: { 0238 KChart::RingDiagram* diagram = new KChart::RingDiagram(this, polarPlane); 0239 polarPlane->replaceDiagram(diagram); 0240 break; 0241 } 0242 default: 0243 break; 0244 } 0245 0246 break; 0247 } 0248 default: // no valid chart types 0249 return; 0250 } 0251 //get the coordinate plane and the diagram for later use 0252 AbstractCoordinatePlane* cPlane = coordinatePlane(); 0253 AbstractDiagram* planeDiagram = cPlane->diagram(); 0254 planeDiagram->setAntiAliasing(true); 0255 0256 // setup chart color palette 0257 switch (config.chartPalette()) { 0258 case eMyMoney::Report::ChartPalette::Application: 0259 switch (KMyMoneySettings::chartsPalette()) { 0260 case 0: 0261 planeDiagram->useDefaultColors(); 0262 break; 0263 case 1: 0264 planeDiagram->useRainbowColors(); 0265 break; 0266 case 2: 0267 default: 0268 planeDiagram->useSubduedColors(); 0269 break; 0270 } 0271 break; 0272 case eMyMoney::Report::ChartPalette::Default: 0273 planeDiagram->useDefaultColors(); 0274 break; 0275 case eMyMoney::Report::ChartPalette::Rainbow: 0276 planeDiagram->useRainbowColors(); 0277 break; 0278 default: 0279 case eMyMoney::Report::ChartPalette::Subdued: 0280 planeDiagram->useSubduedColors(); 0281 break; 0282 } 0283 0284 int eBudgetDiffIdx = rowTypeList.indexOf(eBudgetDiff); 0285 QList<ERowType> myRowTypeList = rowTypeList; 0286 if (eBudgetDiffIdx != -1) 0287 myRowTypeList.removeAt(eBudgetDiffIdx); 0288 QStringList myColumnTypeHeaderList = columnTypeHeaderList; 0289 if (eBudgetDiffIdx != -1) 0290 myColumnTypeHeaderList.removeAt(eBudgetDiffIdx); 0291 int myRowTypeListSize = myRowTypeList.size(); 0292 MyMoneyFile* file = MyMoneyFile::instance(); 0293 int precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); 0294 int rowNum = 0; 0295 QStringList legendNames; 0296 QMultiMap<MyMoneyMoney, int> legendTotal; 0297 0298 switch (config.detailLevel()) { 0299 case eMyMoney::Report::DetailLevel::None: 0300 case eMyMoney::Report::DetailLevel::All: { 0301 // iterate over outer groups 0302 PivotGrid::const_iterator it_outergroup = grid.begin(); 0303 while (it_outergroup != grid.end()) { 0304 //determine whether expenses should be displayed as negative 0305 const bool invertValue = (config.isNegExpenses() && (*it_outergroup).m_inverted); 0306 // iterate over inner groups 0307 PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin(); 0308 while (it_innergroup != (*it_outergroup).end()) { 0309 // iterate over accounts 0310 PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin(); 0311 while (it_row != (*it_innergroup).end()) { 0312 //Do not include investments accounts in the chart because they are merely container of stock and other accounts 0313 if (it_row.key().accountType() != eMyMoney::Account::Type::Investment) { 0314 0315 // get displayed precision 0316 int currencyPrecision = precision; 0317 int securityPrecision = precision; 0318 if (!it_row.key().id().isEmpty()) { 0319 const MyMoneyAccount acc = file->account(it_row.key().id()); 0320 if (acc.isInvest()) { 0321 securityPrecision = file->currency(acc.currencyId()).pricePrecision(); 0322 // stock account isn't evaluated in currency, so take investment account instead 0323 currencyPrecision = MyMoneyMoney::denomToPrec(file->account(acc.parentAccountId()).fraction()); 0324 } else 0325 currencyPrecision = MyMoneyMoney::denomToPrec(acc.fraction()); 0326 } 0327 0328 // iterate row types 0329 for (int i = 0 ; i < myRowTypeListSize; ++i) { 0330 QString legendText; 0331 0332 //only show the column type in the header if there is more than one type 0333 if (myRowTypeListSize > 1) 0334 legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1String(" - ") + it_row.key().name()); 0335 else 0336 legendText = it_row.key().name(); 0337 0338 //set the legend text 0339 legendNames.append(legendText); 0340 legendTotal.insert(it_row.value().value(myRowTypeList.at(i)).m_total.abs(), rowNum); 0341 0342 precision = myRowTypeList.at(i) == ePrice ? securityPrecision : currencyPrecision; 0343 0344 //set the cell value and tooltip 0345 rowNum = drawPivotGridRow(rowNum, it_row.value().value(myRowTypeList.at(i)), 0346 config.isChartDataLabels() ? legendText : QString(), 0347 0, numberColumns, precision, invertValue); 0348 } 0349 } 0350 ++it_row; 0351 } 0352 ++it_innergroup; 0353 } 0354 ++it_outergroup; 0355 } 0356 } 0357 break; 0358 0359 case eMyMoney::Report::DetailLevel::Top: { 0360 // iterate over outer groups 0361 PivotGrid::const_iterator it_outergroup = grid.begin(); 0362 while (it_outergroup != grid.end()) { 0363 //determine whether expenses should be displayed as negative 0364 const bool invertValue = (config.isNegExpenses() && (*it_outergroup).m_inverted); 0365 // iterate over inner groups 0366 PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin(); 0367 while (it_innergroup != (*it_outergroup).end()) { 0368 // iterate row types 0369 for (int i = 0 ; i < myRowTypeListSize; ++i) { 0370 QString legendText; 0371 0372 //only show the column type in the header if there is more than one type 0373 if (myRowTypeListSize > 1) 0374 legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1String(" - ") + it_innergroup.key()); 0375 else 0376 legendText = it_innergroup.key(); 0377 0378 //set the legend text 0379 legendNames.append(legendText); 0380 legendTotal.insert((*it_innergroup).m_total.value(myRowTypeList.at(i)).m_total.abs(), rowNum); 0381 0382 //set the cell value and tooltip 0383 rowNum = drawPivotGridRow(rowNum, (*it_innergroup).m_total.value(myRowTypeList.at(i)), 0384 config.isChartDataLabels() ? legendText : QString(), 0385 0, numberColumns, precision, invertValue); 0386 } 0387 ++it_innergroup; 0388 } 0389 ++it_outergroup; 0390 } 0391 } 0392 break; 0393 0394 case eMyMoney::Report::DetailLevel::Group: { 0395 // iterate over outer groups 0396 PivotGrid::const_iterator it_outergroup = grid.begin(); 0397 while (it_outergroup != grid.end()) { 0398 //determine whether expenses should be displayed as negative 0399 const bool invertValue = (config.isNegExpenses() && (*it_outergroup).m_inverted); 0400 // iterate row types 0401 for (int i = 0 ; i < myRowTypeListSize; ++i) { 0402 QString legendText; 0403 0404 //only show the column type in the header if there is more than one type 0405 if (myRowTypeListSize > 1) 0406 legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1String(" - ") + it_outergroup.key()); 0407 else 0408 legendText = it_outergroup.key(); 0409 0410 //set the legend text 0411 legendNames.append(legendText); 0412 legendTotal.insert((*it_outergroup).m_total.value(myRowTypeList.at(i)).m_total.abs(), rowNum); 0413 0414 //set the cell value and tooltip 0415 rowNum = drawPivotGridRow(rowNum, (*it_outergroup).m_total.value(myRowTypeList.at(i)), 0416 config.isChartDataLabels() ? legendText : QString(), 0417 0, numberColumns, precision, invertValue); 0418 } 0419 ++it_outergroup; 0420 } 0421 0422 //if selected, show totals too 0423 if (config.isShowingRowTotals()) { 0424 // iterate row types 0425 for (int i = 0 ; i < myRowTypeListSize; ++i) { 0426 QString legendText; 0427 0428 //only show the column type in the header if there is more than one type 0429 if (myRowTypeListSize > 1) 0430 legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1String(" - ") + i18nc("Total balance", "Total")); 0431 else 0432 legendText = QString(i18nc("Total balance", "Total")); 0433 0434 //set the legend text 0435 legendNames.append(legendText); 0436 legendTotal.insert(grid.m_total.value(myRowTypeList.at(i)).m_total.abs(), rowNum); 0437 0438 //set the cell value and tooltip 0439 rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)), 0440 config.isChartDataLabels() ? legendText : QString(), 0441 0, numberColumns, precision, false); 0442 } 0443 } 0444 } 0445 break; 0446 0447 case eMyMoney::Report::DetailLevel::Total: { 0448 // iterate row types 0449 for (int i = 0 ; i < myRowTypeListSize; ++i) { 0450 QString legendText; 0451 0452 //only show the column type in the header if there is more than one type 0453 if (myRowTypeListSize > 1) 0454 legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1String(" - ") + i18nc("Total balance", "Total")); 0455 else 0456 legendText = QString(i18nc("Total balance", "Total")); 0457 0458 //set the legend text 0459 legendNames.append(legendText); 0460 legendTotal.insert(grid.m_total.value(myRowTypeList.at(i)).m_total.abs(), rowNum); 0461 0462 //set the cell value and tooltip 0463 if (config.isMixedTime()) { 0464 if (myRowTypeList.at(i) == eActual) 0465 rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)), 0466 config.isChartDataLabels() ? legendText : QString(), 0467 0, config.currentDateColumn(), precision, false); 0468 else if (myRowTypeList.at(i)== eForecast) { 0469 rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)), 0470 config.isChartDataLabels() ? legendText : QString(), 0471 config.currentDateColumn(), numberColumns - config.currentDateColumn(), precision, false); 0472 0473 } else 0474 rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)), 0475 config.isChartDataLabels() ? legendText : QString(), 0476 0, numberColumns, precision, false); 0477 } else 0478 rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)), 0479 config.isChartDataLabels() ? legendText : QString(), 0480 0, numberColumns, precision, false); 0481 } 0482 0483 } 0484 break; 0485 default: 0486 case eMyMoney::Report::DetailLevel::End: 0487 return; 0488 } 0489 0490 auto legendRows = legendTotal.values(); // list of legend rows sorted ascending by total value 0491 for (auto i = 0; i < legendRows.count(); ++i) { 0492 const auto ixRow = legendRows.count() - 1 - i; // take row with the highest total value i.e. form the bottom 0493 const auto row = legendRows.at(ixRow); 0494 if ( row != i) { // if legend isn't sorted by total value, then rearrange model 0495 if ((accountSeries() && !seriesTotals()) || 0496 (seriesTotals() && !accountSeries())) 0497 m_model.insertColumn(i, m_model.takeColumn(row)); 0498 else 0499 m_model.insertRow(i, m_model.takeRow(row)); 0500 0501 for (auto j = i; j < ixRow; ++j) { // fix invalid indexes after above move operation 0502 if (legendRows.at(j) < row) 0503 ++legendRows[j]; 0504 } 0505 legendRows[ixRow] = i; 0506 legendNames.move(row, i); 0507 } 0508 } 0509 0510 // Set up X axis labels (ie "abscissa" to use the technical term) 0511 if (accountSeries()) { // if not, we will set these up while putting in the chart values. 0512 QStringList xLabels; 0513 for (const auto& colHeading : columnHeadings) 0514 xLabels.append(QString(colHeading).replace(QLatin1String(" "), QLatin1String(" "))); 0515 m_model.setVerticalHeaderLabels(xLabels); 0516 } 0517 m_model.setHorizontalHeaderLabels(legendNames); 0518 0519 // set line width for line chart 0520 if (config.chartType() == eMyMoney::Report::ChartType::Line) { 0521 AttributesModel* diagramAttributes = planeDiagram->attributesModel(); 0522 int penWidth = config.chartLineWidth(); 0523 for (int i = 0 ; i < rowNum ; ++i) { 0524 QPen pen = diagramAttributes->headerData(i, Qt::Horizontal, DatasetPenRole).value< QPen >(); 0525 pen.setWidth(penWidth); 0526 m_model.setHeaderData(i, Qt::Horizontal, QVariant::fromValue(pen), DatasetPenRole); 0527 } 0528 } 0529 0530 // set the text attributes after calling replaceLegend() otherwise fon sizes will get overwritten 0531 qreal generalFontSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSizeF(); 0532 if (generalFontSize == -1) 0533 generalFontSize = 8; // this is a fallback if the fontsize was specified in pixels 0534 0535 // the legend is needed only if at least two data sets are rendered 0536 if (qMin(static_cast<int>(KMyMoneySettings::maximumLegendItems()), rowNum) > 1) { 0537 //the legend will be used later 0538 Legend* legend = new Legend(planeDiagram, this); 0539 legend->setTitleText(i18nc("Chart legend title", "Legend")); 0540 0541 //set the legend basic attributes 0542 //this is done after adding the legend because the values are overridden when adding the legend to the chart 0543 const auto maxLegendItems = KMyMoneySettings::maximumLegendItems(); 0544 auto legendItems = legendNames.count(); 0545 0546 auto i = 0; 0547 while (legendItems > maxLegendItems) { 0548 legend->setDatasetHidden(legendRows.at(i++), true); 0549 --legendItems; 0550 } 0551 0552 legend->setUseAutomaticMarkerSize(false); 0553 FrameAttributes legendFrameAttr(legend->frameAttributes()); 0554 legendFrameAttr.setPen(m_foregroundBrush.color()); 0555 // leave some space between the content and the frame 0556 legendFrameAttr.setPadding(2); 0557 legend->setFrameAttributes(legendFrameAttr); 0558 legend->setPosition(Position::East); 0559 legend->setTextAlignment(Qt::AlignLeft); 0560 if (config.isChartDataLabels()) 0561 legend->setLegendStyle(KChart::Legend::MarkersAndLines); 0562 else 0563 legend->setLegendStyle(KChart::Legend::LinesOnly); 0564 replaceLegend(legend); 0565 0566 TextAttributes legendTextAttr(legend->textAttributes()); 0567 legendTextAttr.setPen(m_foregroundBrush.color()); 0568 legendTextAttr.setFontSize(KChart::Measure(generalFontSize, KChartEnums::MeasureCalculationModeAbsolute)); 0569 legend->setTextAttributes(legendTextAttr); 0570 0571 TextAttributes legendTitleTextAttr(legend->titleTextAttributes()); 0572 legendTitleTextAttr.setPen(m_foregroundBrush.color()); 0573 legendTitleTextAttr.setFontSize(KChart::Measure(generalFontSize + 4, KChartEnums::MeasureCalculationModeAbsolute)); 0574 legend->setTitleTextAttributes(legendTitleTextAttr); 0575 } 0576 0577 //set data value attributes 0578 //make sure to show only the required number of fractional digits on the labels of the graph 0579 DataValueAttributes dataValueAttr(planeDiagram->dataValueAttributes()); 0580 MarkerAttributes markerAttr(dataValueAttr.markerAttributes()); 0581 markerAttr.setVisible(true); 0582 markerAttr.setMarkerStyle(MarkerAttributes::MarkerCircle); 0583 dataValueAttr.setMarkerAttributes(markerAttr); 0584 TextAttributes dataValueTextAttr(dataValueAttr.textAttributes()); 0585 dataValueTextAttr.setPen(m_foregroundBrush.color()); 0586 dataValueTextAttr.setFontSize(KChart::Measure(generalFontSize, KChartEnums::MeasureCalculationModeAbsolute)); 0587 dataValueAttr.setTextAttributes(dataValueTextAttr); 0588 m_precision = config.yLabelsPrecision(); 0589 dataValueAttr.setDecimalDigits(config.yLabelsPrecision()); 0590 dataValueAttr.setVisible(config.isChartDataLabels()); 0591 planeDiagram->setDataValueAttributes(dataValueAttr); 0592 planeDiagram->setAllowOverlappingDataValueTexts(false); 0593 0594 m_model.blockSignals(blocked); // reenable dataChanged() signal 0595 0596 //assign model to the diagram 0597 planeDiagram->setModel(&m_model); 0598 0599 // use MyMoney::formatMoney to format the values 0600 const auto rows = m_model.rowCount(); 0601 const auto cols = m_model.columnCount(); 0602 for (int col = 0; col < cols; ++col) { 0603 for (int row = 0; row < rows; ++row) { 0604 const auto idx = m_model.index(row, 0); 0605 auto attrs = planeDiagram->dataValueAttributes(idx); 0606 const MyMoneyMoney value(idx.data(Qt::DisplayRole).toDouble()); 0607 attrs.setDataLabel(value.formatMoney(QString(), m_precision)); 0608 planeDiagram->setDataValueAttributes(idx, attrs); 0609 } 0610 } 0611 adjustVerticalRange(config.yLabelsPrecision()); 0612 } 0613 0614 void KReportChartView::adjustVerticalRange(const int precision) 0615 { 0616 KChart::CartesianCoordinatePlane* cartesianPlane = qobject_cast<CartesianCoordinatePlane*>(coordinatePlane()); 0617 if (!cartesianPlane) 0618 return; // only Cartesian planes are supported 0619 0620 const qreal gridStart = cartesianPlane->gridDimensionsList().at(1).start; 0621 const qreal gridEnd = cartesianPlane->gridDimensionsList().at(1).end; 0622 const qreal precisionLimit = qPow(10, -precision); 0623 qreal dataRangeStart = cartesianPlane->verticalRange().first; 0624 qreal dataRangeEnd = cartesianPlane->verticalRange().second; 0625 0626 bool dataRangeStartAdjusted = false; 0627 bool dataRangeEndAdjusted = false; 0628 0629 if (cartesianPlane->axesCalcModeY() == KChart::AbstractCoordinatePlane::Logarithmic) { 0630 if (dataRangeStart == dataRangeEnd) { 0631 // if data model is represented by a horizontal line, 0632 // extend vertical range by two orders of magnitude upwards and downwards 0633 // if the whole data range is equal to zero, 0634 // at least draw an empty chart with four orders of magnitude on the vertical exis 0635 dataRangeStart = dataRangeStart > 0 ? qPow(10, qFloor(log10(dataRangeStart)) - 2) : precisionLimit; 0636 dataRangeEnd = dataRangeEnd > 0 ? qPow(10, qCeil(log10(dataRangeEnd)) + 2) : qPow(10, -precision + 4); 0637 dataRangeStartAdjusted = true; 0638 dataRangeEndAdjusted = true; 0639 } 0640 0641 if (dataRangeStart < precisionLimit) { 0642 // if data range starts below the precision limit, 0643 // crop the chart to the precision limit 0644 dataRangeStart = precisionLimit; 0645 dataRangeStartAdjusted = true; 0646 } 0647 if (dataRangeEnd < precisionLimit) { 0648 // if data range ends below the precision limit, 0649 // at least draw an empty chart with four orders of magnitude on the vertical axis 0650 dataRangeEnd = qPow(10, -precision + 4); 0651 dataRangeEndAdjusted = true; 0652 } 0653 0654 // if data range boundary needed no adjustment, 0655 // make sure it matches the original grid boundary 0656 if (!dataRangeStartAdjusted) 0657 dataRangeStart = gridStart; 0658 if (!dataRangeEndAdjusted) 0659 dataRangeEnd = gridEnd; 0660 cartesianPlane->setVerticalRange(qMakePair(dataRangeStart, dataRangeEnd)); 0661 } else if (dataRangeStart == dataRangeEnd) // vertical axis type must be Linear then 0662 // if data model is represented by a horizontal line, 0663 // extend vertical range by two upwards and downwards 0664 cartesianPlane->setVerticalRange(qMakePair(dataRangeStart - 2, dataRangeEnd + 2)); 0665 } 0666 0667 int reports::KReportChartView::drawPivotGridRow ( int rowNum, const reports::PivotGridRow& gridRow, const QString& legendText, const int startColumn, const int columnsToDraw, const int precision, const bool invertValue ) 0668 { 0669 // Columns 0670 const QString toolTip = QStringLiteral("<h2>%1</h2><strong>%2</strong><br>"); 0671 const bool isToolTip = !legendText.isEmpty(); 0672 if (seriesTotals()) { 0673 QStandardItem* item = new QStandardItem(); 0674 double value = gridRow.m_total.toDouble(); 0675 item->setData(QVariant(value), Qt::DisplayRole); 0676 if (isToolTip) { 0677 const MyMoneyMoney monetaryValue(value); 0678 item->setToolTip(toolTip.arg(legendText, monetaryValue.formatMoney(QString(), precision))); 0679 } 0680 //set the cell value 0681 if (accountSeries()) { 0682 m_model.insertRows(rowNum, 1); 0683 m_model.setItem(rowNum, 0, item); 0684 } else { 0685 m_model.insertColumns(rowNum, 1); 0686 m_model.setItem(0, rowNum, item); 0687 } 0688 0689 } else { 0690 QList<QStandardItem*> itemList; 0691 for (int i = 0; i < startColumn-1; ++i) { 0692 itemList.append(new QStandardItem); 0693 } 0694 for (int i = startColumn; i < startColumn + columnsToDraw; ++i) { 0695 QStandardItem* item = new QStandardItem(); 0696 if (!m_skipZero || !gridRow.at(i).isZero()) { 0697 double value = gridRow.at(i).toDouble(); 0698 if (invertValue) 0699 value = -value; 0700 item->setData(QVariant(value), Qt::DisplayRole); 0701 if (isToolTip) { 0702 const MyMoneyMoney monetaryValue(value); 0703 item->setToolTip(toolTip.arg(legendText, monetaryValue.formatMoney(QString(), precision))); 0704 } 0705 } 0706 itemList.append(item); 0707 } 0708 if (accountSeries()) 0709 m_model.appendColumn(itemList); 0710 else 0711 m_model.appendRow(itemList); 0712 } 0713 return ++rowNum; 0714 } 0715 0716 void KReportChartView::setDataCell(int row, int column, const double value, QString tip) 0717 { 0718 QMap<int, QVariant> cellMap; 0719 cellMap.insert(Qt::DisplayRole, QVariant(value)); 0720 if (!tip.isEmpty()) 0721 cellMap.insert(Qt::ToolTipRole, QVariant(tip)); 0722 const QModelIndex index = m_model.index(row, column); 0723 m_model.setItemData(index, cellMap); 0724 } 0725 0726 /** 0727 * Justifies the model, so that the given rows and columns fit into it. 0728 */ 0729 void KReportChartView::justifyModelSize(int rows, int columns) 0730 { 0731 const int currentRows = m_model.rowCount(); 0732 const int currentCols = m_model.columnCount(); 0733 0734 if (currentCols < columns) 0735 if (! m_model.insertColumns(currentCols, columns - currentCols)) 0736 qDebug() << "justifyModelSize: could not increase model size."; 0737 if (currentRows < rows) 0738 if (! m_model.insertRows(currentRows, rows - currentRows)) 0739 qDebug() << "justifyModelSize: could not increase model size."; 0740 0741 Q_ASSERT(m_model.rowCount() >= rows); 0742 Q_ASSERT(m_model.columnCount() >= columns); 0743 } 0744 0745 void KReportChartView::setLineWidth(const int lineWidth) 0746 { 0747 LineDiagram* lineDiagram = qobject_cast<LineDiagram*>(coordinatePlane()->diagram()); 0748 if (lineDiagram) { 0749 QList <QPen> pens; 0750 pens = lineDiagram->datasetPens(); 0751 for (int i = 0; i < pens.count(); ++i) { 0752 pens[i].setWidth(lineWidth); 0753 lineDiagram->setPen(i, pens.at(i)); 0754 } 0755 } 0756 } 0757 0758 void KReportChartView::drawLimitLine(const double limit) 0759 { 0760 if (coordinatePlane()->diagram()->datasetDimension() != 1) 0761 return; 0762 // temporarily disconnect the view from the model to avoid update of view on 0763 // emission of the dataChanged() signal for each call of setDataCell(). 0764 // This speeds up the runtime of drawLimitLine() by a factor of 0765 // approx. 60 on my box (1831ms vs. 31ms). 0766 AbstractDiagram* planeDiagram = coordinatePlane()->diagram(); 0767 planeDiagram->setModel(0); 0768 0769 //we get the current number of rows and we add one after that 0770 int row = m_model.rowCount(); 0771 justifyModelSize(m_numColumns, row + 1); 0772 for (int col = 0; col < m_numColumns; ++col) { 0773 setDataCell(col, row, limit); 0774 } 0775 0776 planeDiagram->setModel(&m_model); 0777 0778 //TODO: add format to the line 0779 } 0780 0781 void KReportChartView::removeLegend() 0782 { 0783 Legend* chartLegend = Chart::legend(); 0784 delete chartLegend; 0785 }