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 }