File indexing completed on 2024-05-12 16:33:27
0001 /* This file is part of the KDE project 0002 0003 Copyright 2017 Dag Andersen <danders@get2net.dk> 0004 Copyright 2010 Johannes Simon <johannes.simon@gmail.com> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Library General Public 0008 License as published by the Free Software Foundation; either 0009 version 2 of the License, or (at your option) any later version. 0010 0011 This library is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 Library General Public License for more details. 0015 0016 You should have received a copy of the GNU Library General Public License 0017 along with this library; see the file COPYING.LIB. If not, write to 0018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 * Boston, MA 02110-1301, USA. 0020 */ 0021 0022 // Qt 0023 #include <QRectF> 0024 #include <QTransform> 0025 0026 // KoChart 0027 #include "ChartLayout.h" 0028 #include "Legend.h" 0029 #include "ChartDebug.h" 0030 #include "PlotArea.h" 0031 #include "Axis.h" 0032 #include "ScreenConversions.h" 0033 0034 // Calligra 0035 #include <KoShapeContainer.h> 0036 0037 #include <KChartCartesianAxis.h> 0038 0039 using namespace KoChart; 0040 0041 class ChartLayout::LayoutData 0042 { 0043 public: 0044 int itemType; 0045 QRectF rect; 0046 bool inheritsTransform; 0047 bool clipped; 0048 0049 LayoutData(int _itemType = GenericItemType) 0050 : itemType(_itemType) 0051 , inheritsTransform(true) 0052 , clipped(true) 0053 {} 0054 }; 0055 0056 // static 0057 bool ChartLayout::autoPosition(const KoShape *shape) 0058 { 0059 return shape->additionalStyleAttribute("chart:auto-position") == "true"; 0060 } 0061 0062 // static 0063 bool ChartLayout::autoSize(const KoShape *shape) 0064 { 0065 return shape->additionalStyleAttribute("chart:auto-size") == "true"; 0066 } 0067 0068 ChartLayout::ChartLayout() 0069 : m_doingLayout(false) 0070 , m_relayoutScheduled(false) 0071 , m_padding(5., 5., 5., 5.) 0072 , m_spacing(5., 5.) 0073 , m_layoutingEnabled(true) 0074 { 0075 } 0076 0077 ChartLayout::~ChartLayout() 0078 { 0079 foreach(LayoutData *data, m_layoutItems.values()) 0080 delete data; 0081 } 0082 0083 void ChartLayout::add(KoShape *shape) 0084 { 0085 Q_ASSERT(!m_layoutItems.contains(shape)); 0086 setItemType(shape, GenericItemType); 0087 } 0088 0089 void ChartLayout::remove(KoShape *shape) 0090 { 0091 m_shapes.remove(m_shapes.key(shape)); 0092 if (m_layoutItems.contains(shape)) { 0093 // delete LayoutData 0094 delete m_layoutItems.value(shape); 0095 m_layoutItems.remove(shape); 0096 scheduleRelayout(); 0097 } 0098 } 0099 0100 void ChartLayout::setClipped(const KoShape *shape, bool clipping) 0101 { 0102 Q_ASSERT(m_layoutItems.contains(const_cast<KoShape*>(shape))); 0103 m_layoutItems.value(const_cast<KoShape*>(shape))->clipped = clipping; 0104 } 0105 0106 bool ChartLayout::isClipped(const KoShape *shape) const 0107 { 0108 Q_ASSERT(m_layoutItems.contains(const_cast<KoShape*>(shape))); 0109 return m_layoutItems.value(const_cast<KoShape*>(shape))->clipped; 0110 } 0111 0112 void ChartLayout::setInheritsTransform(const KoShape *shape, bool inherit) 0113 { 0114 m_layoutItems.value(const_cast<KoShape*>(shape))->inheritsTransform = inherit; 0115 } 0116 0117 bool ChartLayout::inheritsTransform(const KoShape *shape) const 0118 { 0119 return m_layoutItems.value(const_cast<KoShape*>(shape))->inheritsTransform; 0120 } 0121 0122 int ChartLayout::count() const 0123 { 0124 return m_layoutItems.size(); 0125 } 0126 0127 QList<KoShape*> ChartLayout::shapes() const 0128 { 0129 return m_layoutItems.keys(); 0130 } 0131 0132 void ChartLayout::setContainerRect(const QRectF &rect) 0133 { 0134 if (rect != m_containerRect) { 0135 m_containerRect = rect; 0136 scheduleRelayout(); 0137 } 0138 } 0139 0140 void ChartLayout::containerChanged(KoShapeContainer *container, KoShape::ChangeType type) 0141 { 0142 switch(type) { 0143 case KoShape::StrokeChanged: 0144 case KoShape::SizeChanged: { 0145 QRectF rect(QPointF(0,0), container->size()); 0146 KoInsets insets = container->strokeInsets(); 0147 rect.adjust(insets.left / 2., insets.top / 2., -insets.right / 2., -insets.bottom / 2.); 0148 setContainerRect(rect); 0149 break; 0150 } 0151 case KoShape::BorderChanged: 0152 warnChartLayout<<"Border not handled"; 0153 break; 0154 default: 0155 break; 0156 } 0157 } 0158 0159 bool ChartLayout::isChildLocked(const KoShape *shape) const 0160 { 0161 return shape->isGeometryProtected(); 0162 } 0163 0164 void ChartLayout::setItemType(const KoShape *shape, ItemType itemType) 0165 { 0166 LayoutData *data = m_layoutItems.value(const_cast<KoShape*>(shape)); 0167 if (!data) { 0168 data = new LayoutData(); 0169 m_layoutItems.insert(const_cast<KoShape*>(shape), data); 0170 } 0171 data->itemType = itemType; 0172 0173 m_shapes.remove(m_shapes.key(const_cast<KoShape*>(shape))); // in case 0174 m_shapes.insert(itemType, const_cast<KoShape*>(shape)); 0175 0176 debugChartLayout<<m_shapes; 0177 scheduleRelayout(); 0178 } 0179 0180 void ChartLayout::setLayoutingEnabled(bool value) 0181 { 0182 m_layoutingEnabled = value; 0183 } 0184 0185 void ChartLayout::proposeMove(KoShape *child, QPointF &move) 0186 { 0187 QRectF current = itemRect(child); 0188 QRectF newRect = current.adjusted(move.x(), move.y(), move.x(), move.y()); 0189 if (newRect.left() < m_containerRect.left()) { 0190 move.setX(m_containerRect.left() - current.left()); 0191 } else if (newRect.right() > m_containerRect.right()) { 0192 move.setX(m_containerRect.right() - current.right()); 0193 } 0194 if (newRect.top() < m_containerRect.top()) { 0195 move.setY(m_containerRect.top() - current.top()); 0196 } else if (newRect.bottom() > m_containerRect.bottom()) { 0197 move.setY(m_containerRect.bottom() - current.bottom()); 0198 } 0199 } 0200 0201 void ChartLayout::childChanged(KoShape *shape, KoShape::ChangeType type) 0202 { 0203 Q_UNUSED(shape); 0204 0205 // Do not relayout again if we're currently in the process of a relayout. 0206 // Repositioning a layout item or resizing it will result in a cull of this method. 0207 if (m_doingLayout) 0208 return; 0209 0210 // This can be fine-tuned, but right now, simply everything will be re-layouted. 0211 switch (type) { 0212 case KoShape::PositionChanged: 0213 case KoShape::SizeChanged: 0214 scheduleRelayout(); 0215 // FIXME: There's some cases that would require relayouting but that don't make sense 0216 // for chart items, e.g. ShearChanged. How should these changes be avoided or handled? 0217 default: 0218 break; 0219 } 0220 } 0221 0222 void ChartLayout::scheduleRelayout() 0223 { 0224 m_relayoutScheduled = true; 0225 } 0226 0227 KChart::CartesianAxis::Position axisPosition(PlotArea *plotarea, ItemType type) 0228 { 0229 KChart::CartesianAxis::Position apos = KChart::CartesianAxis::Bottom; 0230 switch (type) { 0231 case XAxisTitleType: { 0232 if (plotarea && plotarea->xAxis()) { 0233 apos = plotarea->xAxis()->kchartAxisPosition(); 0234 } 0235 break; 0236 } 0237 case YAxisTitleType: { 0238 if (plotarea && plotarea->yAxis()) { 0239 apos = plotarea->yAxis()->kchartAxisPosition(); 0240 } 0241 break; 0242 } 0243 case SecondaryXAxisTitleType: { 0244 if (plotarea && plotarea->secondaryXAxis()) { 0245 apos = plotarea->secondaryXAxis()->kchartAxisPosition(); 0246 } 0247 break; 0248 } 0249 case SecondaryYAxisTitleType: { 0250 if (plotarea && plotarea->secondaryYAxis()) { 0251 apos = plotarea->secondaryYAxis()->kchartAxisPosition(); 0252 } 0253 break; 0254 } 0255 } 0256 return apos; 0257 } 0258 0259 qreal ChartLayout::xOffset(const QRectF &left, const QRectF &right, bool center) const 0260 { 0261 qreal x = (left.width() + (left.width() > 0.0 ? m_spacing.x() : 0.0) - right.width() - (right.width() > 0.0 ? m_spacing.x() : 0.0)); 0262 return center ? x / 2.0 : x; 0263 } 0264 0265 qreal ChartLayout::yOffset(const QRectF &top, const QRectF &bottom, bool center) const 0266 { 0267 qreal y = (top.height() + (top.height() > 0.0 ? m_spacing.y() : 0.0) - bottom.height() - (bottom.height() > 0.0 ? m_spacing.y() : 0.0)); 0268 return center ? y / 2.0 : y; 0269 } 0270 0271 void ChartLayout::rotateAxisTitles(PlotArea *plotarea) { 0272 switch (plotarea->chartType()) { 0273 case BarChartType: 0274 case LineChartType: 0275 case AreaChartType: 0276 case ScatterChartType: 0277 case BubbleChartType: 0278 case StockChartType: { 0279 for (Axis *axis : plotarea->axes()) { 0280 KoShape *title = axis->title(); 0281 title->rotate(-title->rotation()); 0282 switch (axis->kchartAxisPosition()) { 0283 case KChart::CartesianAxis::Left: 0284 title->rotate(-90); 0285 break; 0286 case KChart::CartesianAxis::Right: 0287 title->rotate(90); 0288 break; 0289 default: 0290 break; 0291 } 0292 } 0293 break; 0294 } 0295 default: 0296 break; 0297 } 0298 } 0299 0300 void ChartLayout::layoutAxisTitles(int pos, const QMultiMap<int, KoShape*> &map, const QRectF &rect, const QRectF plotarea) const 0301 { 0302 debugChartLayout<<pos<<rect; 0303 const QList<KoShape*> shapes = map.values(pos); 0304 if (shapes.isEmpty()) { 0305 return; 0306 } 0307 QRectF layoutRect = rect; 0308 QList<QRectF> rects; 0309 switch (pos) { 0310 case 1: // bottom 0311 case 3: // top 0312 layoutRect.adjust((plotarea.center().x() - layoutRect.center().x()) / 2., 0, 0, 0); 0313 for (int i = shapes.count()-1; i >= 0; --i) { 0314 if (!rects.isEmpty()) { 0315 layoutRect.adjust(0, rects.last().height(), 0, 0); 0316 } 0317 QRectF r = layoutRect; 0318 QRectF itmRect = itemRect(shapes[i]); 0319 r.setHeight(itmRect.height()); 0320 if (r.width() > itmRect.width()) { 0321 qreal w = (r.width() - itmRect.width()) / 2; 0322 r.adjust(w, 0, -w, 0); 0323 } 0324 rects.prepend(r); 0325 } 0326 for (int i = 0; i < shapes.count(); ++i) { 0327 m_layoutItems[shapes[i]]->rect = rects.at(i); 0328 debugChartLayout<<(pos==1?"bottom title":"top title")<<i<<':'<<dbg(shapes[i])<<rects.at(i)<<rects; 0329 } 0330 break; 0331 case 2: // left 0332 case 4: // right 0333 layoutRect.adjust(0, (plotarea.center().y() - layoutRect.center().y()) / 2., 0, 0); 0334 for (int i = shapes.count()-1; i >= 0; --i) { 0335 if (!rects.isEmpty()) { 0336 layoutRect.adjust(rects.first().width(), 0, 0, 0); 0337 } 0338 QRectF r = layoutRect; 0339 QRectF itmRect = itemRect(shapes[i]); 0340 r.setWidth(itemRect(shapes[i]).width()); 0341 if (r.height() > itmRect.height()) { 0342 qreal h = (r.height() - itmRect.height()) / 2; 0343 r.adjust(0, h, 0, -h); 0344 } 0345 rects.prepend(r); 0346 } 0347 for (int i = 0; i < shapes.count(); ++i) { 0348 m_layoutItems[shapes[i]]->rect = rects.at(i); 0349 debugChartLayout<<(pos==2?"left title":"right title")<<i<<':'<<dbg(shapes[i])<<rects.at(i)<<rects; 0350 } 0351 break; 0352 } 0353 } 0354 0355 void ChartLayout::calculateLayout() 0356 { 0357 QRectF area = m_containerRect; 0358 area.adjust(m_padding.left, m_padding.top, -m_padding.right, -m_padding.bottom); 0359 0360 if (area.size().width() < 0 && area.size().height() < 0) { 0361 debugChartLayout<<"invalid size:"<<area; 0362 m_doingLayout = false; 0363 m_relayoutScheduled = false; 0364 return; 0365 } 0366 debugChartLayout<<"area:"<<area<<"spacing:"<<m_spacing; 0367 0368 PlotArea *plotarea = dynamic_cast<PlotArea*>(m_shapes.value(PlotAreaType)); 0369 QRectF plotareaRect = area; 0370 if (plotarea->chartType() == BarChartType && plotarea->isVertical()) debugChartLayout<<"Vertical bar chart"; 0371 0372 QRectF titleRect; 0373 KoShape *title = m_shapes.value(TitleLabelType); 0374 if (title && title->isVisible()) { 0375 titleRect = itemRect(title); 0376 } 0377 QRectF subtitleRect; 0378 KoShape *subtitle = m_shapes.value(SubTitleLabelType); 0379 if (subtitle && subtitle->isVisible()) { 0380 subtitleRect = itemRect(subtitle); 0381 } 0382 QRectF footerRect; 0383 KoShape *footer = m_shapes.value(FooterLabelType); 0384 if (footer && footer->isVisible()) { 0385 footerRect = itemRect(footer); 0386 } 0387 QRectF legendRect; 0388 Legend *legend = dynamic_cast<Legend*>(m_shapes.value(LegendType)); 0389 if (legend && legend->isVisible()) { 0390 legendRect = itemRect(legend); 0391 debugChartLayout<<"legend rect:"<<legendRect; 0392 } 0393 0394 rotateAxisTitles(plotarea); 0395 // sort axis titles as bottom, left, top, right 0396 QMultiMap<int, KoShape*> axisTitles; 0397 QRectF bottomTitleRect; // 1 0398 QRectF leftTitleRect; // 2 0399 QRectF topTitleRect; // 3 0400 QRectF rightTitleRect; // 4 0401 0402 KoShape *xtitle = m_shapes.value(XAxisTitleType); 0403 if (xtitle && xtitle->isVisible()) { 0404 switch (axisPosition(plotarea, XAxisTitleType)) { 0405 case KChart::CartesianAxis::Bottom: { 0406 QRectF r = itemRect(xtitle); 0407 if (bottomTitleRect.isNull()) { 0408 bottomTitleRect = r; 0409 } else { 0410 if (bottomTitleRect.width() < r.width()) { 0411 bottomTitleRect.setWidth(r.width()); 0412 } 0413 bottomTitleRect.setHeight(bottomTitleRect.height() + r.height()); 0414 } 0415 axisTitles.insert(1, xtitle); 0416 break; 0417 } 0418 case KChart::CartesianAxis::Left: { 0419 QRectF r = itemRect(xtitle); 0420 if (leftTitleRect.isNull()) { 0421 leftTitleRect = r; 0422 } else { 0423 leftTitleRect.setWidth(leftTitleRect.width() + r.width()); 0424 if (leftTitleRect.height() < r.height()) { 0425 leftTitleRect.setHeight(r.height()); 0426 } 0427 } 0428 axisTitles.insert(2, xtitle); 0429 break; 0430 } 0431 case KChart::CartesianAxis::Top: { 0432 QRectF r = itemRect(xtitle); 0433 if (topTitleRect.isNull()) { 0434 topTitleRect = r; 0435 } else { 0436 if (topTitleRect.width() < r.width()) { 0437 topTitleRect.setWidth(r.width()); 0438 } 0439 topTitleRect.setHeight(topTitleRect.height() + r.height()); 0440 } 0441 axisTitles.insert(3, xtitle); 0442 break; 0443 } 0444 case KChart::CartesianAxis::Right: { 0445 QRectF r = itemRect(xtitle); 0446 if (rightTitleRect.isNull()) { 0447 rightTitleRect = r; 0448 } else { 0449 rightTitleRect.setWidth(rightTitleRect.width() + r.width()); 0450 if (rightTitleRect.height() < r.height()) { 0451 rightTitleRect.setHeight(r.height()); 0452 } 0453 } 0454 axisTitles.insert(4, xtitle); 0455 break; 0456 } 0457 } 0458 } 0459 KoShape *ytitle = m_shapes.value(YAxisTitleType); 0460 if (ytitle && ytitle->isVisible()) { 0461 switch (axisPosition(plotarea, YAxisTitleType)) { 0462 case KChart::CartesianAxis::Bottom: { 0463 QRectF r = itemRect(ytitle); 0464 if (bottomTitleRect.isNull()) { 0465 bottomTitleRect = r; 0466 } else { 0467 if (bottomTitleRect.width() < r.width()) { 0468 bottomTitleRect.setWidth(r.width()); 0469 } 0470 bottomTitleRect.setHeight(bottomTitleRect.height() + r.height()); 0471 } 0472 axisTitles.insert(1, ytitle); 0473 break; 0474 } 0475 case KChart::CartesianAxis::Left: { 0476 QRectF r = itemRect(ytitle); 0477 if (leftTitleRect.isNull()) { 0478 leftTitleRect = r; 0479 } else { 0480 leftTitleRect.setWidth(leftTitleRect.width() + r.width()); 0481 if (leftTitleRect.height() < r.height()) { 0482 leftTitleRect.setHeight(r.height()); 0483 } 0484 } 0485 axisTitles.insert(2, ytitle); 0486 break; 0487 } 0488 case KChart::CartesianAxis::Top: { 0489 QRectF r = itemRect(ytitle); 0490 if (topTitleRect.isNull()) { 0491 topTitleRect = r; 0492 } else { 0493 if (topTitleRect.width() < r.width()) { 0494 topTitleRect.setWidth(r.width()); 0495 } 0496 topTitleRect.setHeight(topTitleRect.height() + r.height()); 0497 } 0498 axisTitles.insert(3, ytitle); 0499 break; 0500 } 0501 case KChart::CartesianAxis::Right: { 0502 QRectF r = itemRect(ytitle); 0503 if (rightTitleRect.isNull()) { 0504 rightTitleRect = r; 0505 } else { 0506 rightTitleRect.setWidth(rightTitleRect.width() + r.width()); 0507 if (rightTitleRect.height() < r.height()) { 0508 rightTitleRect.setHeight(r.height()); 0509 } 0510 } 0511 axisTitles.insert(4, ytitle); 0512 break; 0513 } 0514 } 0515 } 0516 KoShape *sxtitle = m_shapes.value(SecondaryXAxisTitleType); 0517 debugChartLayout<<sxtitle<<(sxtitle && sxtitle->isVisible())<<SecondaryXAxisTitleType; 0518 if (sxtitle && sxtitle->isVisible()) { 0519 switch (axisPosition(plotarea, SecondaryXAxisTitleType)) { 0520 case KChart::CartesianAxis::Bottom: { 0521 QRectF r = itemRect(sxtitle); 0522 if (bottomTitleRect.isNull()) { 0523 bottomTitleRect = r; 0524 } else { 0525 if (bottomTitleRect.width() < r.width()) { 0526 bottomTitleRect.setWidth(r.width()); 0527 } 0528 bottomTitleRect.setHeight(bottomTitleRect.height() + r.height()); 0529 } 0530 axisTitles.insert(1, sxtitle); 0531 break; 0532 } 0533 case KChart::CartesianAxis::Left: { 0534 QRectF r = itemRect(sxtitle); 0535 if (leftTitleRect.isNull()) { 0536 leftTitleRect = r; 0537 } else { 0538 leftTitleRect.setWidth(leftTitleRect.width() + r.width()); 0539 if (leftTitleRect.height() < r.height()) { 0540 leftTitleRect.setHeight(r.height()); 0541 } 0542 } 0543 axisTitles.insert(2, sxtitle); 0544 break; 0545 } 0546 case KChart::CartesianAxis::Top: { 0547 QRectF r = itemRect(sxtitle); 0548 if (topTitleRect.isNull()) { 0549 topTitleRect = r; 0550 } else { 0551 if (topTitleRect.width() < r.width()) { 0552 topTitleRect.setWidth(r.width()); 0553 } 0554 topTitleRect.setHeight(topTitleRect.height() + r.height()); 0555 } 0556 axisTitles.insert(3, sxtitle); 0557 break; 0558 } 0559 case KChart::CartesianAxis::Right: { 0560 QRectF r = itemRect(sxtitle); 0561 if (rightTitleRect.isNull()) { 0562 rightTitleRect = r; 0563 } else { 0564 rightTitleRect.setWidth(rightTitleRect.width() + r.width()); 0565 if (rightTitleRect.height() < r.height()) { 0566 rightTitleRect.setHeight(r.height()); 0567 } 0568 } 0569 axisTitles.insert(4, sxtitle); 0570 break; 0571 } 0572 } 0573 } 0574 KoShape *sytitle = m_shapes.value(SecondaryYAxisTitleType); 0575 if (sytitle && sytitle->isVisible()) { 0576 switch (axisPosition(plotarea, SecondaryYAxisTitleType)) { 0577 case KChart::CartesianAxis::Bottom: { 0578 QRectF r = itemRect(sytitle); 0579 if (bottomTitleRect.isNull()) { 0580 bottomTitleRect = r; 0581 } else { 0582 if (bottomTitleRect.width() < r.width()) { 0583 bottomTitleRect.setWidth(r.width()); 0584 } 0585 bottomTitleRect.setHeight(bottomTitleRect.height() + r.height()); 0586 } 0587 axisTitles.insert(1, sytitle); 0588 break; 0589 } 0590 case KChart::CartesianAxis::Left: { 0591 QRectF r = itemRect(sytitle); 0592 if (leftTitleRect.isNull()) { 0593 leftTitleRect = r; 0594 } else { 0595 leftTitleRect.setWidth(leftTitleRect.width() + r.width()); 0596 if (leftTitleRect.height() < r.height()) { 0597 leftTitleRect.setHeight(r.height()); 0598 } 0599 } 0600 axisTitles.insert(2, sytitle); 0601 break; 0602 } 0603 case KChart::CartesianAxis::Top: { 0604 QRectF r = itemRect(sytitle); 0605 if (topTitleRect.isNull()) { 0606 topTitleRect = r; 0607 } else { 0608 if (topTitleRect.width() < r.width()) { 0609 topTitleRect.setWidth(r.width()); 0610 } 0611 topTitleRect.setHeight(topTitleRect.height() + r.height()); 0612 } 0613 axisTitles.insert(3, sytitle); 0614 break; 0615 } 0616 case KChart::CartesianAxis::Right: { 0617 QRectF r = itemRect(sytitle); 0618 if (rightTitleRect.isNull()) { 0619 rightTitleRect = r; 0620 } else { 0621 rightTitleRect.setWidth(rightTitleRect.width() + r.width()); 0622 if (rightTitleRect.height() < r.height()) { 0623 rightTitleRect.setHeight(r.height()); 0624 } 0625 } 0626 axisTitles.insert(4, sytitle); 0627 break; 0628 } 0629 } 0630 } 0631 0632 0633 QPointF topcenter(area.center().x(), area.top()); 0634 QPointF bottomcenter(area.center().x(), area.bottom()); 0635 QPointF leftcenter(area.left(), area.center().y()); 0636 QPointF rightcenter(area.right(), area.center().y()); 0637 0638 debugChartLayout<<"left:"<<leftcenter<<"right:"<<rightcenter<<"top:"<<topcenter<<"bottom:"<<bottomcenter; 0639 0640 // title/subtitle/footer is aligned with container 0641 if (!titleRect.isNull()) { 0642 if (autoPosition(title)) { 0643 titleRect.moveLeft(topcenter.x() - titleRect.width() / 2.0); 0644 titleRect.moveTop(topcenter.y()); 0645 } 0646 topcenter.setY(titleRect.bottom() + m_spacing.y()); 0647 debugChartLayout<<"title: auto:"<<autoPosition(title)<<titleRect; 0648 } 0649 if (!subtitleRect.isNull()) { 0650 if (autoPosition(subtitle)) { 0651 subtitleRect.moveLeft(topcenter.x() - subtitleRect.width() / 2.0); 0652 subtitleRect.moveTop(topcenter.y()); 0653 } 0654 topcenter.setY(subtitleRect.bottom() + m_spacing.y()); 0655 debugChartLayout<<"subtitle:"<<subtitleRect; 0656 } 0657 if (!footerRect.isNull()) { 0658 if (autoPosition(footer)) { 0659 footerRect.moveLeft(bottomcenter.x() - footerRect.width() / 2.0); 0660 footerRect.moveBottom(bottomcenter.y()); 0661 } 0662 bottomcenter.setY(footerRect.top() - m_spacing.y()); 0663 debugChartLayout<<"footer:"<<footerRect; 0664 } 0665 0666 // all items below shall be aligned with the *final* plot area 0667 plotareaRect.setLeft(leftcenter.x()); 0668 plotareaRect.setTop(topcenter.y()); 0669 plotareaRect.setRight(rightcenter.x()); 0670 plotareaRect.setBottom(bottomcenter.y()); 0671 0672 if (!legendRect.isNull()) { 0673 // Legend is aligned with plot area so axis titles may resize plot area 0674 switch (legend->legendPosition()) { 0675 case StartPosition: 0676 switch(legend->alignment()) { 0677 case Qt::AlignLeft: { 0678 // Align at top of the area to the left of plot area 0679 qreal offset = yOffset(topTitleRect, QRectF()); 0680 legendRect.moveTop(plotareaRect.top() + offset); 0681 break; 0682 } 0683 case Qt::AlignCenter: { 0684 qreal centerOffset = yOffset(topTitleRect, bottomTitleRect, true); 0685 legendRect.moveTop(plotareaRect.center().y() + centerOffset - legendRect.height() / 2.0); 0686 break; 0687 } 0688 case Qt::AlignRight: { 0689 // Align at bottom of the area to the left of plot area 0690 qreal offset = yOffset(QRectF(), bottomTitleRect); 0691 legendRect.moveBottom(plotareaRect.bottom() + offset); 0692 break; 0693 } 0694 } 0695 legendRect.moveLeft(plotareaRect.left()); 0696 plotareaRect.setLeft(legendRect.right() + m_spacing.x()); 0697 break; 0698 case TopPosition: 0699 switch(legend->alignment()) { 0700 case Qt::AlignLeft: { 0701 // Align at left of the area on top of plot area 0702 qreal offset = xOffset(leftTitleRect, QRectF()); 0703 legendRect.moveLeft(plotareaRect.left() + offset); 0704 break; 0705 } 0706 case Qt::AlignCenter: { 0707 qreal centerOffset = xOffset(leftTitleRect, rightTitleRect, true); 0708 legendRect.moveLeft(plotareaRect.center().x() + centerOffset - legendRect.width() / 2.0); 0709 debugChartLayout<<"legend top/center:"<<"to"<<legendRect.left()<<"coffs="<<centerOffset<<"h/2"<<(legendRect.height()/2.0); 0710 break; 0711 } 0712 case Qt::AlignRight: { 0713 // Align at right of the area on top of plot area 0714 qreal offset = xOffset(QRectF(), rightTitleRect); 0715 legendRect.moveRight(plotareaRect.right() + offset); 0716 break; 0717 } 0718 } 0719 legendRect.moveTop(plotareaRect.top()); 0720 plotareaRect.setTop(legendRect.bottom() + m_spacing.y()); 0721 break; 0722 case EndPosition: 0723 switch(legend->alignment()) { 0724 case Qt::AlignLeft: { 0725 // Align at top of the area right of plot area 0726 qreal offset = yOffset(topTitleRect, QRectF()); 0727 legendRect.moveTop(plotareaRect.top() + offset); 0728 debugChartLayout<<"legend end/top:"<<"to"<<legendRect.top()<<"offs="<<offset; 0729 break; 0730 } 0731 case Qt::AlignCenter: { 0732 qreal centerOffset = yOffset(topTitleRect, bottomTitleRect, true); 0733 legendRect.moveTop(plotareaRect.center().y() + centerOffset - legendRect.height() / 2.0); 0734 debugChartLayout<<"legend end/center:"<<"to"<<legendRect.top()<<"coffs="<<centerOffset<<"h/2"<<(legendRect.height()/2.0)<<"pa:"<<plotareaRect.center().y(); 0735 break; 0736 } 0737 case Qt::AlignRight: { 0738 // Align at bottom of the area right of plot area 0739 qreal offset = yOffset(QRectF(), bottomTitleRect); 0740 legendRect.moveBottom(plotareaRect.bottom() + offset); 0741 debugChartLayout<<"legend end/bottom:"<<"to"<<legendRect.bottom()<<"offs="<<offset; 0742 break; 0743 } 0744 } 0745 legendRect.moveRight(plotareaRect.right()); 0746 plotareaRect.setRight(legendRect.left() - m_spacing.x()); 0747 debugChartLayout<<"legend end:"<<legendRect; 0748 break; 0749 case BottomPosition: 0750 switch(legend->alignment()) { 0751 case Qt::AlignLeft: { 0752 // Align at left of the area below of plot area 0753 qreal offset = xOffset(leftTitleRect, QRectF()); 0754 legendRect.moveLeft(plotareaRect.left() + offset); 0755 break; 0756 } 0757 case Qt::AlignCenter: { 0758 qreal centerOffset = xOffset(leftTitleRect, rightTitleRect, true); 0759 legendRect.moveLeft(bottomcenter.x() + centerOffset - legendRect.width() / 2.0); 0760 break; 0761 } 0762 case Qt::AlignRight: { 0763 // Align at right of the area on below plot area 0764 qreal offset = xOffset(QRectF(), rightTitleRect); 0765 legendRect.moveRight(plotareaRect.right() + offset); 0766 break; 0767 } 0768 } 0769 legendRect.moveBottom(plotareaRect.bottom()); 0770 plotareaRect.setBottom(legendRect.top() - m_spacing.y()); 0771 break; 0772 case TopStartPosition: { 0773 qreal xoffset = xOffset(leftTitleRect, QRectF()); 0774 qreal yoffset = yOffset(topTitleRect, QRectF()); 0775 legendRect.moveTopLeft(area.topLeft()); 0776 if (legendRect.right() + m_spacing.x() - xoffset > plotareaRect.left()) { 0777 plotareaRect.setLeft(legendRect.right() + m_spacing.x() - xoffset); 0778 } 0779 if (legendRect.bottom() + m_spacing.y() - yoffset > plotareaRect.top()) { 0780 plotareaRect.setTop(legendRect.bottom() + m_spacing.y() - yoffset); 0781 } 0782 debugChartLayout<<"legend top/start"<<legendRect<<plotareaRect<<"xo:"<<xoffset<<"yo:"<<yoffset; 0783 break; 0784 } 0785 case TopEndPosition: { 0786 qreal xoffset = xOffset(QRectF(), rightTitleRect); 0787 qreal yoffset = yOffset(topTitleRect, QRectF()); 0788 legendRect.moveTopRight(area.topRight()); 0789 if (legendRect.left() - m_spacing.x() + xoffset < plotareaRect.right()) { 0790 plotareaRect.setRight(legendRect.left() - m_spacing.x() + xoffset); 0791 } 0792 if (legendRect.bottom() + m_spacing.y() - yoffset > plotareaRect.top()) { 0793 plotareaRect.setTop(legendRect.bottom() + m_spacing.y() - yoffset); 0794 } 0795 debugChartLayout<<"legend top/end"<<legendRect<<plotareaRect; 0796 break; 0797 } 0798 case BottomStartPosition: { 0799 qreal xoffset = xOffset(leftTitleRect, QRectF()); 0800 qreal yoffset = yOffset(QRectF(), bottomTitleRect); 0801 legendRect.moveBottomLeft(area.bottomLeft()); 0802 if (legendRect.right() + m_spacing.x() - xoffset > plotareaRect.left()) { 0803 plotareaRect.setLeft(legendRect.right() + m_spacing.x() - xoffset); 0804 } 0805 if (legendRect.top() - m_spacing.y() + yoffset < plotareaRect.bottom()) { 0806 plotareaRect.setBottom(legendRect.top() - m_spacing.y() - yoffset); 0807 } 0808 debugChartLayout<<"legend bottom/start"<<legendRect<<plotareaRect<<"offs:"<<xoffset<<','<<yoffset; 0809 break; 0810 } 0811 case BottomEndPosition: { 0812 qreal xoffset = xOffset(QRectF(), rightTitleRect); 0813 qreal yoffset = yOffset(QRectF(), bottomTitleRect); 0814 legendRect.moveBottomRight(area.bottomRight()); 0815 if (legendRect.left() - m_spacing.x() + xoffset < plotareaRect.right()) { 0816 plotareaRect.setRight(legendRect.left() - m_spacing.x() + xoffset); 0817 } 0818 if (legendRect.top() - m_spacing.y() + yoffset < plotareaRect.bottom()) { 0819 plotareaRect.setBottom(legendRect.top() - m_spacing.y() - yoffset); 0820 } 0821 debugChartLayout<<"legend bottom/end"<<legendRect<<plotareaRect; 0822 break; 0823 } 0824 default: 0825 if (plotarea) { 0826 // try to be backwards compatible with loaded shapes 0827 QRectF pr = itemRect(plotarea); // use actual plotarea rect here 0828 debugChartLayout<<"legend:"<<legendRect<<"plot:"<<pr<<legendRect.intersects(pr); 0829 if (!legendRect.intersects(pr) && legendRect.intersects(plotareaRect)) { 0830 if (legendRect.right() < pr.left()) { 0831 plotareaRect.setLeft(qMax(plotareaRect.left(), legendRect.right() + m_spacing.x())); 0832 } else if (legendRect.left() > pr.right()) { 0833 plotareaRect.setRight(qMin(plotareaRect.right(), legendRect.left() - m_spacing.x())); 0834 } else if (legendRect.bottom() < pr.top()) { 0835 plotareaRect.setTop(qMax(plotareaRect.top(), legendRect.bottom() + m_spacing.y())); 0836 } else if (legendRect.top() > pr.bottom()) { 0837 plotareaRect.setBottom(qMin(plotareaRect.bottom(), legendRect.top() - m_spacing.y())); 0838 } 0839 debugChartLayout<<"legend manual:"<<legendRect<<"plot moved to:"<<plotareaRect; 0840 } 0841 } 0842 break; 0843 } 0844 debugChartLayout<<"legend:"<<legendRect<<"pos:"<<legend->legendPosition()<<"align:"<<legend->alignment(); 0845 } 0846 0847 debugChartLayout<<"axis titles:"<<axisTitles; 0848 if (!leftTitleRect.isNull()) { 0849 qreal centerOffset = yOffset(topTitleRect, bottomTitleRect, true); 0850 leftTitleRect.moveTop(plotareaRect.center().y() + centerOffset - leftTitleRect.height() / 2.0); 0851 leftTitleRect.moveLeft(plotareaRect.left()); 0852 debugChartLayout<<"left axis:"<<leftTitleRect; 0853 } 0854 if (!rightTitleRect.isNull()) { 0855 qreal centerOffset = yOffset(topTitleRect, bottomTitleRect, true); 0856 rightTitleRect.moveTop(plotareaRect.center().y() + centerOffset - rightTitleRect.height() / 2.0); 0857 rightTitleRect.moveRight(plotareaRect.right()); 0858 debugChartLayout<<"right axis:"<<rightTitleRect; 0859 } 0860 if (!topTitleRect.isNull()) { 0861 qreal centerOffset = xOffset(leftTitleRect, rightTitleRect, true); 0862 topTitleRect.moveLeft(plotareaRect.center().x() + centerOffset - topTitleRect.width() / 2.0); 0863 topTitleRect.moveTop(plotareaRect.top()); 0864 debugChartLayout<<"top axis:"<<topTitleRect; 0865 } 0866 if (!bottomTitleRect.isNull()) { 0867 qreal centerOffset = xOffset(leftTitleRect, rightTitleRect, true); 0868 bottomTitleRect.moveLeft(plotareaRect.center().x() + centerOffset - bottomTitleRect.width() / 2.0); 0869 bottomTitleRect.moveBottom(plotareaRect.bottom()); 0870 debugChartLayout<<"bottom axis:"<<bottomTitleRect; 0871 } 0872 // now resize plot area 0873 if (!leftTitleRect.isNull()) { 0874 plotareaRect.setLeft(leftTitleRect.right() + m_spacing.x()); 0875 } 0876 if (!rightTitleRect.isNull()) { 0877 plotareaRect.setRight(rightTitleRect.left() - m_spacing.x()); 0878 } 0879 if (!topTitleRect.isNull()) { 0880 plotareaRect.setTop(topTitleRect.bottom() + m_spacing.y()); 0881 } 0882 if (!bottomTitleRect.isNull()) { 0883 plotareaRect.setBottom(bottomTitleRect.top() - m_spacing.y()); 0884 } 0885 if (title && title->isVisible()) { 0886 m_layoutItems[title]->rect = titleRect; 0887 debugChartLayout<<"title"<<dbg(title)<<titleRect; 0888 } 0889 if (subtitle && subtitle->isVisible()) { 0890 m_layoutItems[subtitle]->rect = subtitleRect; 0891 debugChartLayout<<"subtitle"<<dbg(subtitle)<<subtitleRect; 0892 } 0893 if (footer && footer->isVisible()) { 0894 m_layoutItems[footer]->rect = footerRect; 0895 debugChartLayout<<"footer"<<dbg(footer)<<footerRect; 0896 } 0897 if (legend && legend->isVisible()) { 0898 m_layoutItems[legend]->rect = legendRect; 0899 debugChartLayout<<"legend"<<dbg(legend)<<legendRect<<legendRect.center(); 0900 } 0901 0902 layoutAxisTitles(1, axisTitles, bottomTitleRect, plotareaRect); 0903 layoutAxisTitles(2, axisTitles, leftTitleRect, plotareaRect); 0904 layoutAxisTitles(3, axisTitles, topTitleRect, plotareaRect); 0905 layoutAxisTitles(4, axisTitles, rightTitleRect, plotareaRect); 0906 0907 if (plotarea && plotarea->isVisible()) { 0908 m_layoutItems[plotarea]->rect = plotareaRect; 0909 debugChartLayout<<"plot area"<<dbg(plotarea)<<plotareaRect<<plotareaRect.center(); 0910 } 0911 } 0912 0913 void ChartLayout::layout() 0914 { 0915 Q_ASSERT(!m_doingLayout); 0916 0917 if (!m_layoutingEnabled || !m_relayoutScheduled) { 0918 return; 0919 } 0920 m_doingLayout = true; 0921 0922 calculateLayout(); 0923 QMap<KoShape*, LayoutData*>::const_iterator it; 0924 for (it = m_layoutItems.constBegin(); it != m_layoutItems.constEnd(); ++it) { 0925 if (it.key()->isVisible()) { 0926 setItemPosition(it.key(), it.value()->rect.topLeft()); 0927 debugChartLayout<<dbg(it.key())<<it.value()->rect.topLeft()<<itemPosition(it.key()); 0928 if (it.value()->itemType == PlotAreaType) { 0929 setItemSize(it.key(), it.value()->rect.size()); 0930 debugChartLayout<<dbg(it.key())<<it.value()->rect.size()<<itemSize(it.key()); 0931 } 0932 } 0933 } 0934 m_doingLayout = false; 0935 m_relayoutScheduled = false; 0936 } 0937 0938 0939 void ChartLayout::setPadding(const KoInsets &padding) 0940 { 0941 m_padding = padding; 0942 scheduleRelayout(); 0943 } 0944 0945 KoInsets ChartLayout::padding() const 0946 { 0947 return m_padding; 0948 } 0949 0950 void ChartLayout::setSpacing(qreal hSpacing, qreal vSpacing) 0951 { 0952 m_spacing = QPointF(hSpacing, vSpacing); 0953 } 0954 0955 QPointF ChartLayout::spacing() const 0956 { 0957 return m_spacing; 0958 } 0959 0960 /*static*/ QPointF ChartLayout::itemPosition(const KoShape *shape) 0961 { 0962 const QRectF boundingRect = QRectF(QPointF(0, 0), shape->size()); 0963 return shape->transformation().mapRect(boundingRect).topLeft(); 0964 } 0965 0966 /*static*/ QSizeF ChartLayout::itemSize(const KoShape *shape) 0967 { 0968 const QRectF boundingRect = QRectF(QPointF(0, 0), shape->size()); 0969 return shape->transformation().mapRect(boundingRect).size(); 0970 } 0971 0972 /*static*/ void ChartLayout::setItemSize(KoShape *shape, const QSizeF &size) 0973 { 0974 shape->setSize(size); 0975 } 0976 0977 /*static*/ QRectF ChartLayout::itemRect(const KoShape *shape) 0978 { 0979 const QRectF boundingRect = QRectF(itemPosition(shape), itemSize(shape)); 0980 return boundingRect; 0981 } 0982 0983 /*static*/ void ChartLayout::setItemPosition(KoShape *shape, const QPointF& pos) 0984 { 0985 const QPointF offset = shape->position() - itemPosition(shape); 0986 shape->setPosition(pos + offset); 0987 } 0988 0989 QRectF ChartLayout::diagramArea(const KoShape *shape) 0990 { 0991 return diagramArea(shape, itemRect(shape)); 0992 } 0993 0994 // FIXME: Get the actual plot area ex axis labels from KChart 0995 QRectF ChartLayout::diagramArea(const KoShape *shape, const QRectF &rect) 0996 { 0997 const PlotArea* plotArea = dynamic_cast<const PlotArea*>(shape); 0998 if (!plotArea) { 0999 return rect; 1000 } 1001 qreal bottom = 0.0; 1002 qreal left = 0.0; 1003 qreal top = 0.0; 1004 qreal right = 0.0; 1005 // HACK: KChart has some spacing between axis and label 1006 qreal xspace = ScreenConversions::pxToPtX(6.0) * 2.0; 1007 qreal yspace = ScreenConversions::pxToPtY(6.0) * 2.0; 1008 if (plotArea->xAxis() && plotArea->xAxis()->showLabels()) { 1009 bottom = plotArea->xAxis()->fontSize(); 1010 bottom += yspace; 1011 } 1012 if (plotArea->yAxis() && plotArea->yAxis()->showLabels()) { 1013 left = plotArea->yAxis()->fontSize(); 1014 left += xspace; 1015 } 1016 if (plotArea->secondaryXAxis() && plotArea->secondaryXAxis()->showLabels()) { 1017 top = plotArea->secondaryXAxis()->fontSize(); 1018 top += yspace; 1019 } 1020 if (plotArea->secondaryYAxis() && plotArea->secondaryYAxis()->showLabels()) { 1021 right = plotArea->secondaryYAxis()->fontSize(); 1022 right += xspace; 1023 } 1024 return rect.adjusted(left, top, -right, -bottom); 1025 } 1026 1027 QString ChartLayout::dbg(const KoShape *shape) const 1028 { 1029 QString s; 1030 LayoutData *data = m_layoutItems[const_cast<KoShape*>(shape)]; 1031 switch(data->itemType) { 1032 case GenericItemType: s = "KoShape[Generic:"+ shape->shapeId() + "]"; break; 1033 case TitleLabelType: s = "KoShape[ChartTitle]"; break; 1034 case SubTitleLabelType: s = "KoShape[ChartSubTitle]"; break; 1035 case FooterLabelType: s = "KoShape[ChartFooter]"; break; 1036 case PlotAreaType: s = "KoShape[PlotArea]"; break; 1037 case LegendType: 1038 s = "KoShape[Legend"; 1039 switch(static_cast<const Legend*>(shape)->alignment()) { 1040 case Qt::AlignLeft: s += ":Start"; break; 1041 case Qt::AlignCenter: s += ":Center"; break; 1042 case Qt::AlignRight: s += ":End"; break; 1043 default: s += ":Float"; break; 1044 } 1045 s += ']'; 1046 break; 1047 case XAxisTitleType: s = "KoShape[XAxisTitle]"; break; 1048 case YAxisTitleType: s = "KoShape[YAxisTitle]"; break; 1049 case SecondaryXAxisTitleType: s = "KoShape[SXAxisTitle]"; break; 1050 case SecondaryYAxisTitleType: s = "KoShape[SYAxisTitle]"; break; 1051 default: s = "KoShape[Unknown]"; break; 1052 } 1053 return s; 1054 } 1055 1056 QString ChartLayout::dbg(const QList<KoShape*> &shapes) const 1057 { 1058 QString s = "("; 1059 for (int i = 0; i < shapes.count(); ++i) { 1060 if (i > 0) s += ','; 1061 s += dbg(shapes.at(i)); 1062 } 1063 s += ')'; 1064 return s; 1065 }