13 #include "KDChartLegend_p.h"
21 #include <QAbstractTextDocumentLayout>
23 #include <QGridLayout>
26 #include <QTextCharFormat>
27 #include <QTextCursor>
28 #include <QTextDocumentFragment>
29 #include <QTextTableCell>
33 #include <KDABLibFakes>
37 Legend::Private::Private()
39 , alignment(Qt::AlignCenter)
40 , textAlignment(Qt::AlignCenter)
42 , titleText(QObject::tr(
"Legend"))
46 relativePosition.setReferencePoints(
PositionPoints(QPointF(0.0, 0.0)));
47 relativePosition.setReferencePosition(Position::NorthWest);
48 relativePosition.setAlignment(Qt::AlignTop | Qt::AlignLeft);
53 Legend::Private::~Private()
60 Legend::Legend(QWidget *parent)
63 d->referenceArea = parent;
70 d->referenceArea = parent;
82 setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
84 d->layout =
new QGridLayout(
this);
85 d->layout->setContentsMargins(2, 2, 2, 2);
86 d->layout->setSpacing(
d->spacing);
93 textAttrs.
setPen(QPen(Qt::black));
94 textAttrs.
setFont(QFont(QLatin1String(
"helvetica"), 10, QFont::Normal,
false));
100 titleTextAttrs.
setPen(QPen(Qt::black));
101 titleTextAttrs.
setFont(QFont(QLatin1String(
"helvetica"), 12, QFont::Bold,
false));
108 frameAttrs.
setPen(QPen(Qt::black));
113 d->alignment = Qt::AlignCenter;
125 #ifdef DEBUG_LEGEND_PAINT
126 qDebug() <<
"Legend::sizeHint() started";
129 paintItem->sizeHint();
131 return AbstractAreaWidget::sizeHint();
141 #ifdef DEBUG_LEGEND_PAINT
142 qDebug() <<
"Legend::resizeLayout started";
145 d->reflowHDatasetItems(
this);
146 d->layout->setGeometry(QRect(QPoint(0, 0), size));
149 #ifdef DEBUG_LEGEND_PAINT
150 qDebug() <<
"Legend::resizeLayout done";
154 void Legend::activateTheLayout()
156 if (
d->layout &&
d->layout->parent()) {
157 d->layout->activate();
163 if (
d->legendStyle == style) {
166 d->legendStyle = style;
172 return d->legendStyle;
180 auto *legend =
new Legend(
new Private(*
d),
nullptr);
206 #ifdef DEBUG_LEGEND_PAINT
207 qDebug() <<
"entering Legend::paint( QPainter* painter )";
216 paintItem->paint(painter);
219 #ifdef DEBUG_LEGEND_PAINT
220 qDebug() <<
"leaving Legend::paint( QPainter* painter )";
226 int modelLabelsCount = 0;
232 return modelLabelsCount;
237 if (area ==
d->referenceArea) {
240 d->referenceArea = area;
246 return d->referenceArea ?
d->referenceArea : qobject_cast<const QWidget *>(parent());
251 if (
d->observers.isEmpty()) {
254 return d->observers.first()->diagram();
260 for (
int i = 0; i <
d->observers.size(); ++i) {
261 list <<
d->observers.at(i)->diagram();
269 for (
int i = 0; i <
d->observers.size(); ++i) {
270 list <<
d->observers.at(i)->diagram();
283 d->observers[
d->observers.indexOf(oldObs)] = observer;
285 d->observers.append(observer);
297 int datasetBrushOffset = 0;
299 for (
int i = 0; i <
diagrams.count(); i++) {
302 d->brushes.remove(datasetBrushOffset + i);
303 d->texts.remove(datasetBrushOffset + i);
305 for (
int i = 0; i < oldDiagram->
datasetPens().count(); i++) {
306 d->pens.remove(datasetBrushOffset + i);
310 datasetBrushOffset +=
diagrams.at(i)->datasetBrushes().count();
316 d->observers.removeOne(oldObs);
322 QMetaObject::invokeMethod(
this, &Legend::setNeedRebuild, Qt::QueuedConnection);
331 for (
int i = 0; i <
d->observers.size(); ++i) {
332 diagrams.append(
d->observers.at(i)->diagram());
334 for (
int i = 0; i <
diagrams.count(); ++i) {
343 if (!
d->observers.isEmpty() && !old) {
344 old =
d->observers.first()->diagram();
346 d->observers.removeFirst();
361 for (
int i = 0; i <
d->observers.count(); ++i) {
362 if (
d->observers.at(i)->diagram() ==
diagram) {
369 offset = offset +
diagram->model()->columnCount();
389 QWidget::setVisible(visible);
390 emitPositionChanged();
393 void Legend::setNeedRebuild()
405 emitPositionChanged();
408 void Legend::emitPositionChanged()
425 emitPositionChanged();
439 emitPositionChanged();
444 return d->textAlignment;
449 if (
d->legendLineSymbolAlignment ==
alignment) {
453 emitPositionChanged();
458 return d->legendLineSymbolAlignment;
464 if (
d->relativePosition != relativePosition) {
465 d->relativePosition = relativePosition;
466 emitPositionChanged();
472 return d->relativePosition;
482 emitPositionChanged();
487 return d->orientation;
492 if (
d->order == order) {
497 emitPositionChanged();
507 if (
d->showLines == legendShowLines) {
510 d->showLines = legendShowLines;
512 emitPositionChanged();
524 emitPositionChanged();
529 return d->useAutomaticMarkerSize;
539 if (!
d->texts.count()) {
548 if (
d->texts[dataset] ==
text) {
551 d->texts[dataset] =
text;
557 if (
d->texts.find(dataset) !=
d->texts.end()) {
558 return d->texts[dataset];
560 return d->modelLabels[dataset];
571 if (
d->brushes[dataset] != color) {
572 d->brushes[dataset] = color;
580 if (
d->brushes[dataset] !=
brush) {
581 d->brushes[dataset] =
brush;
589 if (
d->brushes.contains(dataset)) {
590 return d->brushes[dataset];
592 return d->modelBrushes[dataset];
603 bool changed =
false;
605 for (
int i = 0; i < datasetBrushes.count(); i++) {
606 if (
d->brushes[i] != datasetBrushes[i]) {
607 d->brushes[i] = datasetBrushes[i];
619 if (
d->pens[dataset] ==
pen) {
622 d->pens[dataset] =
pen;
629 if (
d->pens.find(dataset) !=
d->pens.end()) {
630 return d->pens[dataset];
632 return d->modelPens[dataset];
653 if (
d->markerAttributes.find(dataset) !=
d->markerAttributes.end()) {
654 return d->markerAttributes[dataset];
655 }
else if (
static_cast<uint
>(
d->modelMarkers.count()) > dataset) {
656 return d->modelMarkers[dataset];
664 return d->markerAttributes;
669 if (
d->textAttributes == a) {
672 d->textAttributes = a;
678 return d->textAttributes;
683 if (
d->titleText ==
text) {
697 if (
d->titleTextAttributes == a) {
700 d->titleTextAttributes = a;
706 return d->titleTextAttributes;
711 #ifdef DEBUG_LEGEND_PAINT
712 qDebug() <<
"entering Legend::forceRebuild()";
715 #ifdef DEBUG_LEGEND_PAINT
716 qDebug() <<
"leaving Legend::forceRebuild()";
722 if (
d->spacing == space &&
d->layout->spacing() ==
int(space)) {
726 d->layout->setSpacing(space);
738 for (
int i = 0; i < pal.
size(); i++) {
746 for (
int i = 0; i < pal.
size(); i++) {
755 for (
int i = 0; i < pal.
size(); i++) {
759 static const int s_subduedColorsCount = 18;
760 Q_ASSERT(pal.
size() >= s_subduedColorsCount);
761 static const int order[s_subduedColorsCount] = {
762 0, 5, 10, 15, 2, 7, 12, 17, 4,
763 9, 14, 1, 6, 11, 16, 3, 8, 13};
764 for (
int i = 0; i < s_subduedColorsCount; i++) {
773 #ifdef DEBUG_LEGEND_PAINT
774 qDebug() <<
"Legend::resizeEvent() called";
778 QTimer::singleShot(0,
this, &Legend::emitPositionChanged);
781 void Legend::Private::fetchPaintOptions(
Legend *q)
784 modelBrushes.clear();
786 modelMarkers.clear();
788 for (
int i = 0; i < observers.size(); ++i) {
795 const QList<QPen> diagramPens = diagram->
datasetPens();
796 const QList<MarkerAttributes> diagramMarkers = diagram->
datasetMarkers();
798 const bool ascend = q->
sortOrder() == Qt::AscendingOrder;
799 int dataset = ascend ? 0 : diagramLabels.count() - 1;
800 const int end = ascend ? diagramLabels.count() : -1;
801 for (; dataset != end; dataset += ascend ? 1 : -1) {
805 modelLabels += diagramLabels[dataset];
806 modelBrushes += diagramBrushes[dataset];
807 modelPens += diagramPens[dataset];
808 modelMarkers += diagramMarkers[dataset];
812 Q_ASSERT(modelLabels.count() == modelBrushes.count());
815 QSizeF Legend::Private::markerSize(
Legend *q,
int dataset, qreal fontHeight)
const
819 return QSizeF(fontHeight, fontHeight);
825 QSizeF Legend::Private::maxMarkerSize(
Legend *q, qreal fontHeight)
const
827 QSizeF ret(1.0, 1.0);
829 for (
int dataset = 0; dataset < modelLabels.count(); ++dataset) {
830 ret = ret.expandedTo(markerSize(q, dataset, fontHeight));
836 HDatasetItem::HDatasetItem()
846 w->layout()->update();
850 w = qobject_cast<QWidget *>(w->parent());
856 void Legend::buildLegend()
863 d->destroyOldLayout();
866 d->layout->setColumnStretch(6, 1);
868 d->layout->setColumnStretch(6, 0);
871 d->fetchPaintOptions(
this);
881 measureOrientation,
d->textAlignment);
882 titleItem->setParentWidget(
this);
884 d->paintItems << titleItem;
885 d->layout->addItem(titleItem, 0, 0, 1, 5, Qt::AlignCenter);
890 d->paintItems << lineItem;
891 d->layout->addItem(lineItem, 1, 0, 1, 5, Qt::AlignCenter);
898 tmpFont.setPointSizeF(fontHeight);
902 fontHeight = QFontMetricsF(tmpFont).height();
906 const QSizeF maxMarkerSize =
d->maxMarkerSize(
this, fontHeight);
913 const int lineLengthLeftOfMarker = 8;
915 int maxLineLength = 18;
917 bool hasComplexPenStyle =
false;
918 for (
int dataset = 0; dataset <
d->modelLabels.count(); ++dataset) {
919 const QPen pn =
pen(dataset);
920 const Qt::PenStyle ps = pn.style();
921 if (ps != Qt::NoPen) {
922 maxLineLength = qMax(pn.width() * 18, maxLineLength);
923 if (ps != Qt::SolidLine) {
924 hasComplexPenStyle =
true;
929 maxLineLength += lineLengthLeftOfMarker + int(maxMarkerSize.width());
935 for (
int dataset = 0; dataset <
d->modelLabels.count(); ++dataset) {
936 const int vLayoutRow = 2 + dataset * 2;
942 markerAttrs.
setMarkerSize(
d->markerSize(
this, dataset, fontHeight));
948 markerAttrs.
pen(), Qt::AlignLeft | Qt::AlignVCenter);
952 d->legendLineSymbolAlignment, Qt::AlignCenter);
956 diagram(), maxLineLength,
pen(dataset), lineLengthLeftOfMarker, markerAttrs,
957 markerBrush, markerAttrs.
pen(), Qt::AlignCenter);
964 measureOrientation,
d->textAlignment);
965 dsItem.label->setParentWidget(
this);
970 d->hLayoutDatasets << dsItem;
976 if (dsItem.markerLine) {
977 d->layout->addItem(dsItem.markerLine, vLayoutRow, 1, 1, 1, Qt::AlignCenter);
978 d->paintItems << dsItem.markerLine;
980 d->layout->addItem(dsItem.label, vLayoutRow, 3, 1, 1, Qt::AlignLeft | Qt::AlignVCenter);
981 d->paintItems << dsItem.label;
984 if (
showLines() && dataset !=
d->modelLabels.count() - 1) {
986 d->layout->addItem(lineItem, vLayoutRow + 1, 0, 1, 5, Qt::AlignCenter);
987 d->paintItems << lineItem;
992 d->flowHDatasetItems(
this);
998 d->paintItems << lineItem;
999 d->layout->addItem(lineItem, 2, 2,
d->modelLabels.count() * 2, 1);
1005 #ifdef DEBUG_LEGEND_PAINT
1006 qDebug() <<
"leaving Legend::buildLegend()";
1010 int HDatasetItem::height()
const
1012 return qMax(markerLine->sizeHint().height(), label->sizeHint().height());
1015 void Legend::Private::reflowHDatasetItems(
Legend *q)
1017 if (hLayoutDatasets.isEmpty()) {
1024 for (
int i = layout->count() - 1; i >= 0; i--) {
1025 QLayoutItem *
const item = layout->itemAt(i);
1026 QLayout *
const hbox = item->layout();
1030 paintItems << alItem;
1033 Q_ASSERT(
dynamic_cast<QHBoxLayout *
>(hbox));
1036 for (
int j = hbox->count() - 1; j >= 0; j--) {
1042 flowHDatasetItems(q);
1047 void Legend::Private::flowHDatasetItems(
Legend *q)
1049 const int separatorLineWidth = 3;
1053 auto *currentLine =
new QHBoxLayout;
1054 int mainLayoutRow = 1;
1055 layout->addItem(currentLine, mainLayoutRow++, 0,
1056 1, 5, Qt::AlignLeft | Qt::AlignVCenter);
1058 for (
int dataset = 0; dataset < hLayoutDatasets.size(); dataset++) {
1059 HDatasetItem *hdsItem = &hLayoutDatasets[dataset];
1061 bool spacerUsed =
false;
1062 bool separatorUsed =
false;
1063 if (!currentLine->isEmpty()) {
1064 const int separatorWidth = (q->
showLines() ? separatorLineWidth : 0) + q->
spacing();
1065 const int payloadWidth = hdsItem->markerLine->sizeHint().width() + hdsItem->label->sizeHint().width();
1066 if (currentLine->sizeHint().width() + separatorWidth + payloadWidth > allowedWidth) {
1068 #ifdef DEBUG_LEGEND_PAINT
1069 qDebug() << Q_FUNC_INFO <<
"break" << mainLayoutRow
1070 << currentLine->sizeHint().width()
1071 << currentLine->sizeHint().width() + separatorWidth + payloadWidth
1074 currentLine =
new QHBoxLayout;
1075 layout->addItem(currentLine, mainLayoutRow++, 0,
1076 1, 5, Qt::AlignLeft | Qt::AlignVCenter);
1079 if (!hdsItem->spacer) {
1080 hdsItem->spacer =
new QSpacerItem(q->
spacing(), 1);
1082 currentLine->addItem(hdsItem->spacer);
1086 if (!hdsItem->separatorLine) {
1089 paintItems << hdsItem->separatorLine;
1090 currentLine->addItem(hdsItem->separatorLine);
1091 separatorUsed =
true;
1097 delete hdsItem->spacer;
1098 hdsItem->spacer =
nullptr;
1100 if (!separatorUsed) {
1101 delete hdsItem->separatorLine;
1102 hdsItem->separatorLine =
nullptr;
1105 currentLine->addItem(hdsItem->markerLine);
1106 paintItems << hdsItem->markerLine;
1107 currentLine->addItem(hdsItem->label);
1108 paintItems << hdsItem->label;
1117 return !
d->hLayoutDatasets.isEmpty();
1122 if (
d->hLayoutDatasets.isEmpty()) {
1128 for (
int i = 0; i < 2; i++) {
1129 if (QLayoutItem *item =
d->layout->itemAtPosition(i, 0)) {
1130 ret += item->sizeHint().height();
1133 const int separatorLineWidth = 3;
1135 int currentLineWidth = 0;
1136 int currentLineHeight = 0;
1137 for (
const HDatasetItem &hdsItem : qAsConst(
d->hLayoutDatasets)) {
1138 const int payloadWidth = hdsItem.markerLine->sizeHint().width() + hdsItem.label->sizeHint().width();
1139 if (!currentLineWidth) {
1141 currentLineWidth = payloadWidth;
1143 const int separatorWidth = (
showLines() ? separatorLineWidth : 0) +
spacing();
1144 currentLineWidth += separatorWidth + payloadWidth;
1145 if (currentLineWidth > width) {
1147 #ifdef DEBUG_LEGEND_PAINT
1148 qDebug() << Q_FUNC_INFO <<
"heightForWidth break" << currentLineWidth
1149 << currentLineWidth + separatorWidth + payloadWidth
1152 ret += currentLineHeight +
spacing();
1153 currentLineWidth = payloadWidth;
1154 currentLineHeight = 0;
1157 currentLineHeight = qMax(currentLineHeight, hdsItem.height());
1159 ret += currentLineHeight;
1163 void Legend::Private::destroyOldLayout()
1167 for (
int i = layout->count() - 1; i >= 0; i--) {
1168 delete layout->takeAt(i);
1170 Q_ASSERT(!layout->count());
1171 hLayoutDatasets.clear();
1182 return d->hiddenDatasets;
1187 if (hidden && !
d->hiddenDatasets.contains(dataset)) {
1188 d->hiddenDatasets.append(dataset);
1189 }
else if (!hidden &&
d->hiddenDatasets.contains(dataset)) {
1190 d->hiddenDatasets.removeAll(dataset);
1196 return d->hiddenDatasets.contains(dataset);
static void updateToplevelLayout(QWidget *w)
@ MeasureCalculationModeAbsolute
@ MeasureOrientationMinimum
@ MeasureOrientationHorizontal
void setFrameAttributes(const FrameAttributes &a)
bool compare(const AbstractAreaBase *other) const
FrameAttributes frameAttributes() const
AbstractDiagram defines the interface for diagram classes.
QStringList datasetLabels() const
QList< QBrush > datasetBrushes() const
QList< QPen > datasetPens() const
QList< MarkerAttributes > datasetMarkers() const
A DiagramObserver watches the associated diagram for changes and deletion and emits corresponding sig...
const AbstractDiagram * diagram() const
void diagramDataHidden(AbstractDiagram *diagram)
void diagramAboutToBeDestroyed(AbstractDiagram *diagram)
void diagramDataChanged(AbstractDiagram *diagram)
void diagramAttributesChanged(AbstractDiagram *diagram)
A set of attributes for frames around items.
void setVisible(bool visible)
void setPen(const QPen &pen)
void setPadding(int padding)
static QPaintDevice * paintDevice()
Legend defines the interface for the legend drawing class.
const RelativePosition floatingPosition() const
uint datasetCount() const
void setOrientation(Qt::Orientation orientation)
void setVisible(bool visible) override
Qt::Alignment textAlignment() const
Returns the alignment used while rendering text elements within the legend.
LegendStyle legendStyle() const
bool compare(const Legend *other) const
void destroyedLegend(Legend *)
void setSortOrder(Qt::SortOrder order)
const QMap< uint, QString > texts() const
const QMap< uint, QBrush > brushes() const
void setShowLines(bool legendShowLines)
void resetTexts()
Removes all legend texts that might have been set by setText.
QSize minimumSizeHint() const override
void paint(QPainter *painter) override
DiagramList diagrams() const
void setMarkerAttributes(uint dataset, const MarkerAttributes &)
ConstDiagramList constDiagrams() const
void setDiagram(KDChart::AbstractDiagram *newDiagram)
A convenience method doing the same as replaceDiagram( newDiagram, 0 );.
void forceRebuild() override
QBrush brush(uint dataset) const
void setPosition(Position position)
Specify the position of a non-floating legend.
QString titleText() const
void setHiddenDatasets(const QList< uint > &hiddenDatasets)
void setBrushesFromDiagram(KDChart::AbstractDiagram *diagram)
TextAttributes titleTextAttributes() const
Qt::Alignment legendSymbolAlignment() const
Returns the alignment used while drawing legend symbol(alignment of Legend::LinesOnly) within the leg...
MarkerAttributes markerAttributes(uint dataset) const
void replaceDiagram(KDChart::AbstractDiagram *newDiagram, KDChart::AbstractDiagram *oldDiagram=nullptr)
uint dataSetOffset(KDChart::AbstractDiagram *diagram)
void setDatasetHidden(uint dataset, bool hidden)
void resizeEvent(QResizeEvent *event) override
void needSizeHint() override
void setUseAutomaticMarkerSize(bool useAutomaticMarkerSize)
const QMap< uint, QPen > pens() const
void setColor(uint dataset, const QColor &color)
void setLegendSymbolAlignment(Qt::Alignment)
Specify the alignment of the legend symbol( alignment of Legend::LinesOnly) within the legend.
virtual Legend * clone() const
void resizeLayout(const QSize &size) override
Qt::Orientation orientation() const
int heightForWidth(int width) const override
void addDiagram(KDChart::AbstractDiagram *newDiagram)
bool datasetIsHidden(uint dataset) const
Qt::Alignment alignment() const
void setPen(uint dataset, const QPen &pen)
QSize sizeHint() const override
void setReferenceArea(const QWidget *area)
const QWidget * referenceArea() const
void setTextAttributes(const TextAttributes &a)
void setTextAlignment(Qt::Alignment)
Specify the alignment of the text elements within the legend.
KDChart::AbstractDiagram * diagram() const
void setLegendStyle(LegendStyle style)
void removeDiagram(KDChart::AbstractDiagram *oldDiagram)
void setBrush(uint dataset, const QBrush &brush)
Position position() const
bool useAutomaticMarkerSize() const
void setSpacing(uint space)
Legend(QWidget *parent=nullptr)
void setText(uint dataset, const QString &text)
const QMap< uint, MarkerAttributes > markerAttributes() const
const QList< uint > hiddenDatasets() const
void setAlignment(Qt::Alignment)
Specify the alignment of a non-floating legend.
QPen pen(uint dataset) const
bool hasHeightForWidth() const override
void setFloatingPosition(const RelativePosition &relativePosition)
TextAttributes textAttributes() const
void setTitleTextAttributes(const TextAttributes &a)
Qt::SortOrder sortOrder() const
void setSubduedColors(bool ordered=false)
void setTitleText(const QString &text)
QString text(uint dataset) const
A set of attributes controlling the appearance of data set markers.
QColor markerColor() const
void setMarkerSize(const QSizeF &size)
QSizeF markerSize() const
Measure is used to specify relative and absolute sizes in KDChart, e.g. font sizes.
A Palette is a set of brushes (or colors) to be used for painting data sets.
QBrush getBrush(int position) const
static const Palette & subduedPalette()
static const Palette & defaultPalette()
static const Palette & rainbowPalette()
Stores the absolute target points of a Position.
Defines a position, using compass terminology.
static const Position & Floating
static const Position & NorthEast
Defines relative position information: reference area, position in this area (reference position),...
A set of text attributes.
void setFontSize(const Measure &measure)
void setMinimalFontSize(const Measure &measure)
void setPen(const QPen &pen)
void setFont(const QFont &font)
qreal calculatedFontSize(const QSizeF &referenceSize, KDChartEnums::MeasureOrientation autoReferenceOrientation) const
Returns the font size that is used at drawing time.
QList< const AbstractDiagram * > ConstDiagramList
QList< AbstractDiagram * > DiagramList