qlegend.cpp
533 lines
| 13.3 KiB
| text/x-c
|
CppLexer
/ src / qlegend.cpp
Jani Honkonen
|
r794 | /**************************************************************************** | ||
** | ||||
** Copyright (C) 2012 Digia Plc | ||||
** All rights reserved. | ||||
** For any questions to Digia, please use contact form at http://qt.digia.com | ||||
** | ||||
** This file is part of the Qt Commercial Charts Add-on. | ||||
** | ||||
** $QT_BEGIN_LICENSE$ | ||||
** Licensees holding valid Qt Commercial licenses may use this file in | ||||
** accordance with the Qt Commercial License Agreement provided with the | ||||
** Software or, alternatively, in accordance with the terms contained in | ||||
** a written agreement between you and Digia. | ||||
** | ||||
** If you have questions regarding the use of this file, please use | ||||
** contact form at http://qt.digia.com | ||||
** $QT_END_LICENSE$ | ||||
** | ||||
****************************************************************************/ | ||||
sauimone
|
r524 | #include "qlegend.h" | ||
Michal Klocek
|
r790 | #include "qchart_p.h" | ||
sauimone
|
r524 | #include "qseries.h" | ||
sauimone
|
r547 | #include "legendmarker_p.h" | ||
sauimone
|
r565 | #include "qxyseries.h" | ||
#include "qlineseries.h" | ||||
#include "qareaseries.h" | ||||
#include "qscatterseries.h" | ||||
#include "qsplineseries.h" | ||||
#include "qbarseries.h" | ||||
#include "qstackedbarseries.h" | ||||
#include "qpercentbarseries.h" | ||||
#include "qbarset.h" | ||||
#include "qpieseries.h" | ||||
#include "qpieslice.h" | ||||
sauimone
|
r626 | #include "chartpresenter_p.h" | ||
sauimone
|
r529 | #include <QPainter> | ||
#include <QPen> | ||||
Michal Klocek
|
r855 | #include <QTimer> | ||
sauimone
|
r524 | |||
sauimone
|
r547 | #include <QGraphicsSceneEvent> | ||
sauimone
|
r529 | |||
sauimone
|
r547 | QTCOMMERCIALCHART_BEGIN_NAMESPACE | ||
sauimone
|
r529 | |||
sauimone
|
r728 | /*! | ||
\class QLegend | ||||
\brief part of QtCommercial chart API. | ||||
QLegend is a graphical object, whics displays legend of the chart. Legend state is updated by QChart, when | ||||
series have been changed. By default, legend is drawn by QChart, but user can set a new parent to legend and | ||||
handle the drawing manually. | ||||
User isn't supposed to create or delete legend objects, but can reference it via QChart class. | ||||
\mainclass | ||||
\sa QChart, QSeries | ||||
*/ | ||||
sauimone
|
r724 | /*! | ||
sauimone
|
r888 | \enum QLegend::Alignment | ||
sauimone
|
r724 | |||
This enum describes the possible position for legend inside chart. | ||||
sauimone
|
r888 | \value AlignmentTop | ||
\value AlignmentBottom | ||||
\value AlignmentLeft | ||||
\value AlignmentRight | ||||
sauimone
|
r724 | */ | ||
/*! | ||||
sauimone
|
r888 | \fn qreal QLegend::minWidth() const | ||
Returns minimum width of the legend | ||||
sauimone
|
r724 | */ | ||
/*! | ||||
sauimone
|
r888 | \fn qreal QLegend::minHeight() const | ||
Returns minimum height of the legend | ||||
sauimone
|
r724 | */ | ||
/*! | ||||
Constructs the legend object and sets the parent to \a parent | ||||
*/ | ||||
Michal Klocek
|
r855 | |||
Michal Klocek
|
r791 | QLegend::QLegend(QChart *chart):QGraphicsWidget(chart), | ||
sauimone
|
r783 | m_margin(5), | ||
Michal Klocek
|
r855 | m_offsetX(0), | ||
m_offsetY(0), | ||||
sauimone
|
r785 | m_brush(Qt::darkGray), // TODO: default should come from theme | ||
sauimone
|
r800 | m_alignment(QLegend::AlignmentTop), | ||
Michal Klocek
|
r855 | m_markers(new QGraphicsItemGroup(this)), | ||
sauimone
|
r803 | m_attachedToChart(true), | ||
Michal Klocek
|
r855 | m_chart(chart), | ||
m_minWidth(0), | ||||
m_minHeight(0), | ||||
m_width(0), | ||||
m_height(0), | ||||
sauimone
|
r882 | m_backgroundVisible(false) | ||
sauimone
|
r762 | { | ||
sauimone
|
r626 | setZValue(ChartPresenter::LegendZValue); | ||
Michal Klocek
|
r855 | setFlags(QGraphicsItem::ItemClipsChildrenToShape); | ||
sauimone
|
r882 | setVisible(false); // By default legend is invisible | ||
sauimone
|
r524 | } | ||
sauimone
|
r724 | /*! | ||
Paints the legend to given \a painter. Paremeters \a option and \a widget arent used. | ||||
*/ | ||||
Michal Klocek
|
r855 | |||
sauimone
|
r524 | void QLegend::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) | ||
{ | ||||
Michal Klocek
|
r855 | Q_UNUSED(option) | ||
Q_UNUSED(widget) | ||||
sauimone
|
r882 | if(!m_backgroundVisible) return; | ||
Tero Ahola
|
r611 | |||
sauimone
|
r724 | painter->setOpacity(opacity()); | ||
Michal Klocek
|
r645 | painter->setPen(m_pen); | ||
painter->setBrush(m_brush); | ||||
Michal Klocek
|
r855 | painter->drawRect(boundingRect()); | ||
sauimone
|
r524 | } | ||
sauimone
|
r724 | /*! | ||
Bounding rect of legend. | ||||
*/ | ||||
Michal Klocek
|
r855 | |||
sauimone
|
r524 | QRectF QLegend::boundingRect() const | ||
{ | ||||
Michal Klocek
|
r855 | return m_rect; | ||
sauimone
|
r524 | } | ||
sauimone
|
r724 | /*! | ||
Sets the \a brush of legend. Brush affects the background of legend. | ||||
*/ | ||||
Tero Ahola
|
r737 | void QLegend::setBrush(const QBrush &brush) | ||
sauimone
|
r540 | { | ||
Tero Ahola
|
r737 | if (m_brush != brush) { | ||
Michal Klocek
|
r645 | m_brush = brush; | ||
update(); | ||||
} | ||||
} | ||||
sauimone
|
r724 | /*! | ||
Returns the brush used by legend. | ||||
*/ | ||||
Michal Klocek
|
r645 | QBrush QLegend::brush() const | ||
{ | ||||
return m_brush; | ||||
} | ||||
sauimone
|
r724 | /*! | ||
Sets the \a pen of legend. Pen affects the legend borders. | ||||
*/ | ||||
Tero Ahola
|
r737 | void QLegend::setPen(const QPen &pen) | ||
Michal Klocek
|
r645 | { | ||
Tero Ahola
|
r737 | if (m_pen != pen) { | ||
Michal Klocek
|
r645 | m_pen = pen; | ||
update(); | ||||
} | ||||
sauimone
|
r540 | } | ||
sauimone
|
r724 | /*! | ||
Returns the pen used by legend | ||||
*/ | ||||
Michal Klocek
|
r645 | QPen QLegend::pen() const | ||
sauimone
|
r540 | { | ||
Michal Klocek
|
r645 | return m_pen; | ||
sauimone
|
r540 | } | ||
sauimone
|
r724 | /*! | ||
sauimone
|
r888 | Sets the \a alignment for legend. Legend tries to paint itself on the defined position in chart. | ||
\sa QLegend::Alignment | ||||
sauimone
|
r724 | */ | ||
Jani Honkonen
|
r907 | void QLegend::setAlignment(QLegend::Alignments alignment) | ||
sauimone
|
r616 | { | ||
Michal Klocek
|
r855 | if(m_alignment!=alignment && m_attachedToChart) { | ||
sauimone
|
r803 | m_alignment = alignment; | ||
updateLayout(); | ||||
Michal Klocek
|
r855 | } | ||
sauimone
|
r716 | } | ||
sauimone
|
r724 | /*! | ||
Returns the preferred layout for legend | ||||
*/ | ||||
sauimone
|
r803 | QLegend::Alignments QLegend::alignment() const | ||
sauimone
|
r716 | { | ||
sauimone
|
r766 | return m_alignment; | ||
sauimone
|
r616 | } | ||
sauimone
|
r724 | /*! | ||
\internal \a series \a domain Should be called when series is added to chart. | ||||
*/ | ||||
Tero Ahola
|
r737 | void QLegend::handleSeriesAdded(QSeries *series, Domain *domain) | ||
sauimone
|
r524 | { | ||
Tero Ahola
|
r611 | Q_UNUSED(domain) | ||
sauimone
|
r792 | switch (series->type()) | ||
{ | ||||
case QSeries::SeriesTypeLine: { | ||||
QLineSeries *lineSeries = static_cast<QLineSeries *>(series); | ||||
appendMarkers(lineSeries); | ||||
break; | ||||
} | ||||
case QSeries::SeriesTypeArea: { | ||||
QAreaSeries *areaSeries = static_cast<QAreaSeries *>(series); | ||||
appendMarkers(areaSeries); | ||||
break; | ||||
} | ||||
case QSeries::SeriesTypeBar: { | ||||
QBarSeries *barSeries = static_cast<QBarSeries *>(series); | ||||
appendMarkers(barSeries); | ||||
break; | ||||
} | ||||
case QSeries::SeriesTypeStackedBar: { | ||||
QStackedBarSeries *stackedBarSeries = static_cast<QStackedBarSeries *>(series); | ||||
appendMarkers(stackedBarSeries); | ||||
break; | ||||
} | ||||
case QSeries::SeriesTypePercentBar: { | ||||
QPercentBarSeries *percentBarSeries = static_cast<QPercentBarSeries *>(series); | ||||
appendMarkers(percentBarSeries); | ||||
break; | ||||
} | ||||
case QSeries::SeriesTypeScatter: { | ||||
QScatterSeries *scatterSeries = static_cast<QScatterSeries *>(series); | ||||
appendMarkers(scatterSeries); | ||||
break; | ||||
} | ||||
case QSeries::SeriesTypePie: { | ||||
QPieSeries *pieSeries = static_cast<QPieSeries *>(series); | ||||
appendMarkers(pieSeries); | ||||
connect(pieSeries,SIGNAL(added(QList<QPieSlice*>)),this,SLOT(handleAdded(QList<QPieSlice*>))); | ||||
break; | ||||
} | ||||
case QSeries::SeriesTypeSpline: { | ||||
QSplineSeries *splineSeries = static_cast<QSplineSeries *>(series); | ||||
appendMarkers(splineSeries); | ||||
break; | ||||
} | ||||
default: { | ||||
qWarning()<< "QLegend::handleSeriesAdded" << series->type() << "unknown series type."; | ||||
break; | ||||
} | ||||
} | ||||
Michal Klocek
|
r871 | updateLayout(); | ||
sauimone
|
r586 | } | ||
sauimone
|
r724 | /*! | ||
\internal \a series Should be called when series is removed from chart. | ||||
*/ | ||||
Tero Ahola
|
r737 | void QLegend::handleSeriesRemoved(QSeries *series) | ||
sauimone
|
r586 | { | ||
sauimone
|
r792 | switch (series->type()) | ||
{ | ||||
case QSeries::SeriesTypeArea: { | ||||
QAreaSeries *areaSeries = static_cast<QAreaSeries *>(series); | ||||
deleteMarkers(areaSeries); | ||||
break; | ||||
} | ||||
case QSeries::SeriesTypePie: { | ||||
QPieSeries *pieSeries = static_cast<QPieSeries *>(series); | ||||
disconnect(pieSeries, SIGNAL(added(QList<QPieSlice *>)), this, SLOT(handleAdded(QList<QPieSlice *>))); | ||||
deleteMarkers(series); | ||||
break; | ||||
} | ||||
default: { | ||||
// All other types | ||||
sauimone
|
r586 | deleteMarkers(series); | ||
sauimone
|
r792 | break; | ||
} | ||||
sauimone
|
r586 | } | ||
sauimone
|
r716 | updateLayout(); | ||
sauimone
|
r586 | } | ||
sauimone
|
r724 | /*! | ||
\internal \a slices Should be called when slices are added to pie chart. | ||||
*/ | ||||
Tero Ahola
|
r737 | void QLegend::handleAdded(QList<QPieSlice *> slices) | ||
sauimone
|
r637 | { | ||
Tero Ahola
|
r737 | QPieSeries* series = static_cast<QPieSeries *> (sender()); | ||
Michal Klocek
|
r855 | foreach(QPieSlice* slice, slices) { | ||
PieLegendMarker* marker = new PieLegendMarker(series,slice, this); | ||||
m_markers->addToGroup(marker); | ||||
sauimone
|
r637 | } | ||
sauimone
|
r716 | updateLayout(); | ||
sauimone
|
r637 | } | ||
sauimone
|
r724 | /*! | ||
\internal \a slices Should be called when slices are removed from pie chart. Currently unused, | ||||
because removed slices are also deleted and we listen destroyed signal | ||||
*/ | ||||
sauimone
|
r643 | void QLegend::handleRemoved(QList<QPieSlice *> slices) | ||
{ | ||||
Tero Ahola
|
r647 | Q_UNUSED(slices) | ||
sauimone
|
r643 | } | ||
sauimone
|
r799 | /*! | ||
Detaches the legend from chart. Chart won't change layout of the legend. | ||||
*/ | ||||
void QLegend::detachFromChart() | ||||
{ | ||||
m_attachedToChart = false; | ||||
} | ||||
/*! | ||||
Attaches the legend to chart. Chart may change layout of the legend. | ||||
*/ | ||||
void QLegend::attachToChart() | ||||
{ | ||||
m_attachedToChart = true; | ||||
} | ||||
/*! | ||||
Returns true, if legend is attached to chart. | ||||
*/ | ||||
Michal Klocek
|
r855 | bool QLegend::isAttachedToChart() | ||
sauimone
|
r799 | { | ||
return m_attachedToChart; | ||||
} | ||||
sauimone
|
r724 | /*! | ||
sauimone
|
r792 | \internal Helper function. Appends markers from \a series to legend. | ||
sauimone
|
r724 | */ | ||
sauimone
|
r792 | void QLegend::appendMarkers(QAreaSeries* series) | ||
sauimone
|
r586 | { | ||
Michal Klocek
|
r855 | AreaLegendMarker* marker = new AreaLegendMarker(series,this); | ||
m_markers->addToGroup(marker); | ||||
sauimone
|
r576 | } | ||
sauimone
|
r724 | /*! | ||
\internal Helper function. Appends markers from \a series to legend. | ||||
*/ | ||||
sauimone
|
r586 | void QLegend::appendMarkers(QXYSeries* series) | ||
sauimone
|
r529 | { | ||
Michal Klocek
|
r855 | XYLegendMarker* marker = new XYLegendMarker(series,this); | ||
m_markers->addToGroup(marker); | ||||
sauimone
|
r565 | } | ||
sauimone
|
r724 | /*! | ||
sauimone
|
r792 | \internal Helper function. Appends markers from \a series to legend. | ||
sauimone
|
r724 | */ | ||
sauimone
|
r586 | void QLegend::appendMarkers(QBarSeries *series) | ||
sauimone
|
r565 | { | ||
sauimone
|
r786 | foreach(QBarSet* set, series->barSets()) { | ||
Michal Klocek
|
r855 | BarLegendMarker* marker = new BarLegendMarker(series,set, this); | ||
m_markers->addToGroup(marker); | ||||
sauimone
|
r529 | } | ||
sauimone
|
r565 | } | ||
sauimone
|
r529 | |||
sauimone
|
r724 | /*! | ||
sauimone
|
r792 | \internal Helper function. Appends markers from \a series to legend. | ||
sauimone
|
r724 | */ | ||
sauimone
|
r586 | void QLegend::appendMarkers(QPieSeries *series) | ||
sauimone
|
r565 | { | ||
sauimone
|
r786 | foreach(QPieSlice* slice, series->slices()) { | ||
Michal Klocek
|
r855 | PieLegendMarker* marker = new PieLegendMarker(series,slice, this); | ||
m_markers->addToGroup(marker); | ||||
sauimone
|
r529 | } | ||
} | ||||
sauimone
|
r724 | /*! | ||
\internal Deletes all markers that are created from \a series | ||||
*/ | ||||
sauimone
|
r586 | void QLegend::deleteMarkers(QSeries *series) | ||
{ | ||||
// Search all markers that belong to given series and delete them. | ||||
Michal Klocek
|
r855 | |||
QList<QGraphicsItem *> items = m_markers->childItems(); | ||||
Michal Klocek
|
r870 | foreach (QGraphicsItem *markers, items) { | ||
LegendMarker *marker = static_cast<LegendMarker*>(markers); | ||||
Michal Klocek
|
r855 | if (marker->series() == series) { | ||
delete marker; | ||||
sauimone
|
r586 | } | ||
} | ||||
} | ||||
sauimone
|
r724 | /*! | ||
\internal Updates layout of legend. Tries to fit as many markers as possible up to the maximum size of legend. | ||||
If items don't fit, sets the visibility of scroll buttons accordingly. | ||||
Causes legend to be resized. | ||||
*/ | ||||
sauimone
|
r529 | |||
Michal Klocek
|
r855 | void QLegend::setOffset(const QPointF& point) | ||
{ | ||||
sauimone
|
r626 | |||
Michal Klocek
|
r855 | switch(m_alignment) { | ||
sauimone
|
r716 | |||
Michal Klocek
|
r855 | case AlignmentTop: | ||
case AlignmentBottom: { | ||||
if(m_width<=m_rect.width()) return; | ||||
sauimone
|
r810 | |||
Michal Klocek
|
r855 | if (point.x() != m_offsetX) { | ||
m_offsetX = qBound(0.0, point.x(), m_width - m_rect.width()); | ||||
m_markers->setPos(-m_offsetX,m_rect.top()); | ||||
sauimone
|
r626 | } | ||
Michal Klocek
|
r855 | break; | ||
sauimone
|
r616 | } | ||
Michal Klocek
|
r855 | case AlignmentLeft: | ||
case AlignmentRight: { | ||||
sauimone
|
r716 | |||
Michal Klocek
|
r855 | if(m_height<=m_rect.height()) return; | ||
sauimone
|
r716 | |||
Michal Klocek
|
r855 | if (point.y() != m_offsetY) { | ||
m_offsetY = qBound(0.0, point.y(), m_height - m_rect.height()); | ||||
m_markers->setPos(m_rect.left(),-m_offsetY); | ||||
sauimone
|
r716 | } | ||
Michal Klocek
|
r855 | break; | ||
sauimone
|
r716 | } | ||
sauimone
|
r810 | } | ||
Michal Klocek
|
r855 | } | ||
sauimone
|
r716 | |||
Michal Klocek
|
r855 | QPointF QLegend::offset() const | ||
{ | ||||
return QPointF(m_offsetX,m_offsetY); | ||||
sauimone
|
r716 | } | ||
Michal Klocek
|
r855 | // this function runs first to set min max values | ||
void QLegend::updateLayout() | ||||
sauimone
|
r716 | { | ||
Michal Klocek
|
r855 | m_offsetX=0; | ||
QList<QGraphicsItem *> items = m_markers->childItems(); | ||||
if(items.isEmpty()) return; | ||||
m_minWidth=0; | ||||
m_minHeight=0; | ||||
switch(m_alignment) { | ||||
case AlignmentTop: | ||||
case AlignmentBottom: { | ||||
QPointF point = m_rect.topLeft(); | ||||
m_width = 0; | ||||
foreach (QGraphicsItem *item, items) { | ||||
item->setPos(point.x(),m_rect.height()/2 -item->boundingRect().height()/2); | ||||
const QRectF& rect = item->boundingRect(); | ||||
qreal w = rect.width(); | ||||
m_minWidth=qMax(m_minWidth,w); | ||||
m_minHeight=qMax(m_minHeight,rect.height()); | ||||
m_width+=w; | ||||
point.setX(point.x() + w); | ||||
} | ||||
if(m_width<m_rect.width()){ | ||||
m_markers->setPos(m_rect.width()/2-m_width/2,m_rect.top()); | ||||
}else{ | ||||
m_markers->setPos(m_rect.topLeft()); | ||||
} | ||||
m_height=m_minHeight; | ||||
} | ||||
break; | ||||
case AlignmentLeft: | ||||
case AlignmentRight:{ | ||||
QPointF point = m_rect.topLeft(); | ||||
m_height = 0; | ||||
foreach (QGraphicsItem *item, items) { | ||||
item->setPos(point); | ||||
const QRectF& rect = item->boundingRect(); | ||||
qreal h = rect.height(); | ||||
m_minWidth=qMax(m_minWidth,rect.width()); | ||||
m_minHeight=qMax(m_minHeight,h); | ||||
m_height+=h; | ||||
point.setY(point.y() + h); | ||||
} | ||||
if(m_height<m_rect.height()){ | ||||
m_markers->setPos(m_rect.left(),m_rect.height()/2-m_height/2); | ||||
}else{ | ||||
m_markers->setPos(m_rect.topLeft()); | ||||
} | ||||
m_width=m_minWidth; | ||||
} | ||||
break; | ||||
} | ||||
sauimone
|
r716 | |||
Michal Klocek
|
r855 | m_chart->d_ptr->m_presenter->updateLayout(); //TODO fixme; | ||
sauimone
|
r716 | } | ||
sauimone
|
r888 | /*! | ||
Sets the visibility of legend background to \a visible | ||||
*/ | ||||
Michal Klocek
|
r855 | void QLegend::setBackgroundVisible(bool visible) | ||
{ | ||||
sauimone
|
r882 | if(m_backgroundVisible!=visible) | ||
Michal Klocek
|
r855 | { | ||
sauimone
|
r882 | m_backgroundVisible=visible; | ||
Michal Klocek
|
r855 | update(); | ||
sauimone
|
r716 | } | ||
} | ||||
sauimone
|
r888 | /*! | ||
Returns the visibility of legend background | ||||
*/ | ||||
Michal Klocek
|
r855 | bool QLegend::isBackgroundVisible() const | ||
{ | ||||
sauimone
|
r882 | return m_backgroundVisible; | ||
Michal Klocek
|
r855 | } | ||
sauimone
|
r716 | |||
sauimone
|
r888 | /*! | ||
\internal \a event see QGraphicsWidget for details | ||||
*/ | ||||
Michal Klocek
|
r855 | void QLegend::resizeEvent(QGraphicsSceneResizeEvent *event) | ||
{ | ||||
const QRectF& rect = QRectF(QPoint(0,0),event->newSize()); | ||||
QGraphicsWidget::resizeEvent(event); | ||||
if(m_rect != rect){ | ||||
m_rect = rect; | ||||
updateLayout(); | ||||
sauimone
|
r716 | } | ||
Michal Klocek
|
r855 | } | ||
sauimone
|
r716 | |||
sauimone
|
r888 | /*! | ||
\internal \a event see QGraphicsWidget for details | ||||
*/ | ||||
Michal Klocek
|
r855 | void QLegend::hideEvent(QHideEvent *event) | ||
{ | ||||
QGraphicsWidget::hideEvent(event); | ||||
setEnabled(false); | ||||
updateLayout(); | ||||
sauimone
|
r716 | } | ||
sauimone
|
r888 | /*! | ||
\internal \a event see QGraphicsWidget for details | ||||
*/ | ||||
Michal Klocek
|
r855 | void QLegend::showEvent(QShowEvent *event) | ||
sauimone
|
r716 | { | ||
Michal Klocek
|
r855 | QGraphicsWidget::showEvent(event); | ||
setEnabled(true); | ||||
updateLayout(); | ||||
} | ||||
sauimone
|
r724 | |||
sauimone
|
r524 | #include "moc_qlegend.cpp" | ||
Tero Ahola
|
r737 | |||
sauimone
|
r524 | QTCOMMERCIALCHART_END_NAMESPACE | ||