charttheme.cpp
353 lines
| 10.8 KiB
| text/x-c
|
CppLexer
/ src / charttheme.cpp
Tero Ahola
|
r103 | #include "charttheme_p.h" | ||
#include "qchart.h" | ||||
Michal Klocek
|
r176 | #include "qchartaxis.h" | ||
Jani Honkonen
|
r324 | #include <QTime> | ||
Tero Ahola
|
r103 | |||
Michal Klocek
|
r143 | //series | ||
sauimone
|
r214 | #include "qbarset.h" | ||
sauimone
|
r338 | #include "qbarseries.h" | ||
#include "qstackedbarseries.h" | ||||
#include "qpercentbarseries.h" | ||||
Michal Klocek
|
r349 | #include "qlineseries.h" | ||
Michal Klocek
|
r421 | #include "qareaseries.h" | ||
Tero Ahola
|
r182 | #include "qscatterseries.h" | ||
Jani Honkonen
|
r163 | #include "qpieseries.h" | ||
Jani Honkonen
|
r203 | #include "qpieslice.h" | ||
Marek Rosa
|
r401 | #include "qsplineseries.h" | ||
Jani Honkonen
|
r163 | |||
Michal Klocek
|
r143 | //items | ||
#include "axisitem_p.h" | ||||
sauimone
|
r381 | #include "barpresenter_p.h" | ||
#include "stackedbarpresenter_p.h" | ||||
#include "percentbarpresenter_p.h" | ||||
Michal Klocek
|
r145 | #include "linechartitem_p.h" | ||
Michal Klocek
|
r421 | #include "areachartitem_p.h" | ||
Michal Klocek
|
r470 | #include "scatterchartitem_p.h" | ||
Marek Rosa
|
r419 | #include "piepresenter_p.h" | ||
Marek Rosa
|
r460 | #include "splinechartitem_p.h" | ||
Tero Ahola
|
r103 | |||
Michal Klocek
|
r143 | //themes | ||
Jani Honkonen
|
r494 | #include "chartthemedefault_p.h" | ||
Michal Klocek
|
r143 | #include "chartthemevanilla_p.h" | ||
#include "chartthemeicy_p.h" | ||||
#include "chartthemegrayscale_p.h" | ||||
#include "chartthemescientific_p.h" | ||||
Tero Ahola
|
r103 | |||
Tero Ahola
|
r125 | |||
Michal Klocek
|
r143 | QTCOMMERCIALCHART_BEGIN_NAMESPACE | ||
Michal Klocek
|
r153 | ChartTheme::ChartTheme(QChart::ChartTheme id) | ||
Michal Klocek
|
r143 | { | ||
Michal Klocek
|
r153 | m_id = id; | ||
Jani Honkonen
|
r324 | qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); | ||
Michal Klocek
|
r143 | } | ||
Michal Klocek
|
r153 | ChartTheme* ChartTheme::createTheme(QChart::ChartTheme theme) | ||
Michal Klocek
|
r143 | { | ||
switch(theme) { | ||||
case QChart::ChartThemeVanilla: | ||||
return new ChartThemeVanilla(); | ||||
case QChart::ChartThemeIcy: | ||||
return new ChartThemeIcy(); | ||||
case QChart::ChartThemeGrayscale: | ||||
return new ChartThemeGrayscale(); | ||||
case QChart::ChartThemeScientific: | ||||
return new ChartThemeScientific(); | ||||
Jani Honkonen
|
r494 | default: | ||
return new ChartThemeDefault(); | ||||
Michal Klocek
|
r143 | } | ||
} | ||||
void ChartTheme::decorate(QChart* chart) | ||||
{ | ||||
Jani Honkonen
|
r494 | chart->setChartBackgroundBrush(m_backgroundGradient); | ||
Michal Klocek
|
r143 | } | ||
//TODO helper to by removed later | ||||
Michal Klocek
|
r360 | void ChartTheme::decorate(ChartItem* item, QSeries* series,int count) | ||
Michal Klocek
|
r143 | { | ||
switch(series->type()) | ||||
{ | ||||
Michal Klocek
|
r360 | case QSeries::SeriesTypeLine: { | ||
Michal Klocek
|
r349 | QLineSeries* s = static_cast<QLineSeries*>(series); | ||
Michal Klocek
|
r145 | LineChartItem* i = static_cast<LineChartItem*>(item); | ||
Michal Klocek
|
r143 | decorate(i,s,count); | ||
break; | ||||
} | ||||
Michal Klocek
|
r421 | case QSeries::SeriesTypeArea: { | ||
QAreaSeries* s = static_cast<QAreaSeries*>(series); | ||||
AreaChartItem* i = static_cast<AreaChartItem*>(item); | ||||
decorate(i,s,count); | ||||
break; | ||||
} | ||||
Michal Klocek
|
r360 | case QSeries::SeriesTypeBar: { | ||
sauimone
|
r338 | QBarSeries* b = static_cast<QBarSeries*>(series); | ||
sauimone
|
r216 | BarPresenter* i = static_cast<BarPresenter*>(item); | ||
Michal Klocek
|
r143 | decorate(i,b,count); | ||
break; | ||||
} | ||||
Michal Klocek
|
r360 | case QSeries::SeriesTypeStackedBar: { | ||
sauimone
|
r338 | QStackedBarSeries* s = static_cast<QStackedBarSeries*>(series); | ||
sauimone
|
r216 | StackedBarPresenter* i = static_cast<StackedBarPresenter*>(item); | ||
Michal Klocek
|
r143 | decorate(i,s,count); | ||
break; | ||||
} | ||||
Michal Klocek
|
r360 | case QSeries::SeriesTypePercentBar: { | ||
sauimone
|
r338 | QPercentBarSeries* s = static_cast<QPercentBarSeries*>(series); | ||
sauimone
|
r216 | PercentBarPresenter* i = static_cast<PercentBarPresenter*>(item); | ||
Michal Klocek
|
r143 | decorate(i,s,count); | ||
break; | ||||
} | ||||
Michal Klocek
|
r360 | case QSeries::SeriesTypeScatter: { | ||
Tero Ahola
|
r195 | QScatterSeries* s = qobject_cast<QScatterSeries*>(series); | ||
Q_ASSERT(s); | ||||
Michal Klocek
|
r470 | ScatterChartItem* i = static_cast<ScatterChartItem*>(item); | ||
Tero Ahola
|
r195 | Q_ASSERT(i); | ||
decorate(i, s, count); | ||||
break; | ||||
} | ||||
Michal Klocek
|
r360 | case QSeries::SeriesTypePie: { | ||
Jani Honkonen
|
r163 | QPieSeries* s = static_cast<QPieSeries*>(series); | ||
PiePresenter* i = static_cast<PiePresenter*>(item); | ||||
decorate(i,s,count); | ||||
break; | ||||
} | ||||
Michal Klocek
|
r143 | default: | ||
qDebug()<<"Wrong item to be decorated by theme"; | ||||
Tero Ahola
|
r103 | break; | ||
} | ||||
Michal Klocek
|
r143 | |||
Tero Ahola
|
r103 | } | ||
Michal Klocek
|
r421 | void ChartTheme::decorate(AreaChartItem* item, QAreaSeries* series,int count) | ||
{ | ||||
QPen pen; | ||||
QBrush brush; | ||||
Tero Ahola
|
r516 | if (pen != series->pen()){ | ||
Michal Klocek
|
r421 | item->setPen(series->pen()); | ||
Tero Ahola
|
r516 | } else { | ||
pen.setColor(colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), 1.0)); | ||||
Michal Klocek
|
r421 | pen.setWidthF(2); | ||
item->setPen(pen); | ||||
} | ||||
Tero Ahola
|
r516 | if (brush != series->brush()) { | ||
Michal Klocek
|
r421 | item->setBrush(series->brush()); | ||
Tero Ahola
|
r516 | } else { | ||
QBrush brush(m_seriesColors.at(count % m_seriesColors.size())); | ||||
Michal Klocek
|
r421 | item->setBrush(brush); | ||
} | ||||
} | ||||
Michal Klocek
|
r349 | void ChartTheme::decorate(LineChartItem* item, QLineSeries* series,int count) | ||
Tero Ahola
|
r103 | { | ||
Michal Klocek
|
r152 | QPen pen; | ||
if(pen != series->pen()){ | ||||
Michal Klocek
|
r476 | item->setLinePen(series->pen()); | ||
Michal Klocek
|
r152 | return; | ||
} | ||||
Jani Honkonen
|
r494 | pen.setColor(m_seriesColors.at(count%m_seriesColors.size())); | ||
Michal Klocek
|
r152 | pen.setWidthF(2); | ||
Michal Klocek
|
r476 | item->setLinePen(pen); | ||
Tero Ahola
|
r103 | } | ||
sauimone
|
r338 | void ChartTheme::decorate(BarPresenter* item, QBarSeries* series,int count) | ||
Tero Ahola
|
r108 | { | ||
sauimone
|
r357 | QList<QBarSet*> sets = series->barSets(); | ||
sauimone
|
r509 | for (int i=0; i<sets.count(); i++) { | ||
qreal pos = (qreal) i / (qreal) sets.count(); | ||||
QColor c = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), pos); | ||||
sets.at(i)->setBrush(QBrush(c)); | ||||
sauimone
|
r512 | |||
// Pick label color as far as possible from bar color (within gradient). | ||||
// 0.3 is magic number that was picked as value that gave enough contrast with icy theme gradient :) | ||||
// TODO: better picking of label color? | ||||
if (pos < 0.3) { | ||||
c = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), 1); | ||||
} else { | ||||
c = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), 0); | ||||
} | ||||
sets.at(i)->setFloatingValuePen(QPen(c)); | ||||
sauimone
|
r164 | } | ||
Tero Ahola
|
r108 | } | ||
sauimone
|
r338 | void ChartTheme::decorate(StackedBarPresenter* item, QStackedBarSeries* series,int count) | ||
Tero Ahola
|
r103 | { | ||
sauimone
|
r357 | QList<QBarSet*> sets = series->barSets(); | ||
sauimone
|
r509 | for (int i=0; i<sets.count(); i++) { | ||
qreal pos = (qreal) i / (qreal) sets.count(); | ||||
QColor c = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), pos); | ||||
sets.at(i)->setBrush(QBrush(c)); | ||||
sauimone
|
r512 | |||
if (pos < 0.3) { | ||||
c = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), 1); | ||||
} else { | ||||
c = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), 0); | ||||
} | ||||
sets.at(i)->setFloatingValuePen(QPen(c)); | ||||
sauimone
|
r164 | } | ||
Tero Ahola
|
r103 | } | ||
sauimone
|
r338 | void ChartTheme::decorate(PercentBarPresenter* item, QPercentBarSeries* series,int count) | ||
Michal Klocek
|
r143 | { | ||
sauimone
|
r357 | QList<QBarSet*> sets = series->barSets(); | ||
sauimone
|
r509 | for (int i=0; i<sets.count(); i++) { | ||
qreal pos = (qreal) i / (qreal) sets.count(); | ||||
QColor c = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), pos); | ||||
sets.at(i)->setBrush(QBrush(c)); | ||||
sauimone
|
r512 | |||
if (pos < 0.3) { | ||||
c = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), 1); | ||||
} else { | ||||
c = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), 0); | ||||
} | ||||
sets.at(i)->setFloatingValuePen(QPen(c)); | ||||
sauimone
|
r164 | } | ||
Michal Klocek
|
r143 | } | ||
Tero Ahola
|
r103 | |||
Michal Klocek
|
r470 | void ChartTheme::decorate(ScatterChartItem* item, QScatterSeries* series, int count) | ||
Tero Ahola
|
r182 | { | ||
Michal Klocek
|
r470 | Q_ASSERT(item); | ||
Tero Ahola
|
r182 | Q_ASSERT(series); | ||
Tero Ahola
|
r514 | // Use a base color for brush | ||
item->setBrush(m_seriesColors.at(count % m_seriesColors.size())); | ||||
Tero Ahola
|
r195 | |||
Tero Ahola
|
r514 | // Take pen near from gradient start, effectively using a lighter color for outline | ||
QPen pen(QBrush(Qt::SolidPattern), 3); | ||||
Tero Ahola
|
r516 | pen.setColor(colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), 1.0)); | ||
Michal Klocek
|
r470 | item->setPen(pen); | ||
Tero Ahola
|
r182 | } | ||
Jani Honkonen
|
r494 | void ChartTheme::decorate(PiePresenter* item, QPieSeries* series, int count) | ||
Jani Honkonen
|
r163 | { | ||
Tero Ahola
|
r507 | // Get color for a slice from a gradient linearly, beginning from the start of the gradient | ||
for (int i(0); i < series->slices().count(); i++) { | ||||
qreal pos = (qreal) i / (qreal) series->count(); | ||||
Tero Ahola
|
r510 | QColor penColor = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), 0.1); | ||
series->slices().at(i)->setSlicePen(penColor); | ||||
QColor brushColor = colorAt(m_seriesGradients.at(count % m_seriesGradients.size()), pos); | ||||
series->slices().at(i)->setSliceBrush(brushColor); | ||||
Jani Honkonen
|
r163 | } | ||
} | ||||
Tero Ahola
|
r103 | |||
Jani Honkonen
|
r494 | void ChartTheme::decorate(QChartAxis* axis, AxisItem* item) | ||
Michal Klocek
|
r176 | { | ||
//TODO: dummy defults for now | ||||
Michal Klocek
|
r223 | axis->setLabelsBrush(Qt::black); | ||
axis->setLabelsPen(Qt::NoPen); | ||||
axis->setShadesPen(Qt::NoPen); | ||||
axis->setShadesOpacity(0.5); | ||||
Michal Klocek
|
r176 | } | ||
Marek Rosa
|
r460 | void ChartTheme::decorate(SplineChartItem* item, QSplineSeries* series, int count) | ||
Marek Rosa
|
r401 | { | ||
Marek Rosa
|
r460 | Q_ASSERT(item); | ||
Marek Rosa
|
r401 | Q_ASSERT(series); | ||
Marek Rosa
|
r434 | QPen pen; | ||
Michal Klocek
|
r476 | |||
Marek Rosa
|
r434 | if(pen != series->pen()){ | ||
Michal Klocek
|
r476 | item->setLinePen(series->pen()); | ||
}else{ | ||||
Jani Honkonen
|
r494 | pen.setColor(m_seriesColors.at(count%m_seriesColors.size())); | ||
Michal Klocek
|
r476 | pen.setWidthF(series->pen().widthF()); | ||
item->setLinePen(series->pen()); | ||||
Marek Rosa
|
r434 | } | ||
Jani Honkonen
|
r494 | // QColor color = m_seriesColors.at(count % m_seriesColors.size()); | ||
Marek Rosa
|
r401 | // TODO: define alpha in the theme? or in the series? | ||
//color.setAlpha(120); | ||||
// QBrush brush(color, Qt::SolidPattern); | ||||
// presenter->m_markerBrush = brush; | ||||
// QPen pen(brush, 3); | ||||
// pen.setColor(color); | ||||
// presenter->m_markerPen = pen; | ||||
} | ||||
Tero Ahola
|
r507 | void ChartTheme::generateSeriesGradients() | ||
{ | ||||
// Generate gradients in HSV color space | ||||
foreach (QColor color, m_seriesColors) { | ||||
QLinearGradient g; | ||||
qreal h = color.hsvHueF(); | ||||
qreal s = color.hsvSaturationF(); | ||||
// TODO: tune the algorithm to give nice results with most base colors defined in | ||||
// most themes. The rest of the gradients we can define manually in theme specific | ||||
// implementation. | ||||
QColor start = color; | ||||
start.setHsvF(h, 0.05, 0.95); | ||||
g.setColorAt(0.0, start); | ||||
g.setColorAt(0.5, color); | ||||
QColor end = color; | ||||
end.setHsvF(h, s, 0.25); | ||||
g.setColorAt(1.0, end); | ||||
m_seriesGradients << g; | ||||
} | ||||
} | ||||
Jani Honkonen
|
r494 | QColor ChartTheme::colorAt(const QColor &start, const QColor &end, qreal pos) | ||
{ | ||||
Q_ASSERT(pos >=0.0 && pos <= 1.0); | ||||
qreal r = start.redF() + ((end.redF() - start.redF()) * pos); | ||||
qreal g = start.greenF() + ((end.greenF() - start.greenF()) * pos); | ||||
qreal b = start.blueF() + ((end.blueF() - start.blueF()) * pos); | ||||
QColor c; | ||||
c.setRgbF(r, g, b); | ||||
return c; | ||||
} | ||||
QColor ChartTheme::colorAt(const QGradient &gradient, qreal pos) | ||||
{ | ||||
Q_ASSERT(pos >=0 && pos <= 1.0); | ||||
// another possibility: | ||||
// http://stackoverflow.com/questions/3306786/get-intermediate-color-from-a-gradient | ||||
QGradientStops stops = gradient.stops(); | ||||
int count = stops.count(); | ||||
// find previous stop relative to position | ||||
QGradientStop prev = stops.first(); | ||||
for (int i=0; i<count; i++) { | ||||
QGradientStop stop = stops.at(i); | ||||
if (pos > stop.first) | ||||
prev = stop; | ||||
// given position is actually a stop position? | ||||
if (pos == stop.first) { | ||||
//qDebug() << "stop color" << pos; | ||||
return stop.second; | ||||
} | ||||
} | ||||
// find next stop relative to position | ||||
QGradientStop next = stops.last(); | ||||
for (int i=count-1; i>=0; i--) { | ||||
QGradientStop stop = stops.at(i); | ||||
if (pos < stop.first) | ||||
next = stop; | ||||
} | ||||
//qDebug() << "prev" << prev.first << "pos" << pos << "next" << next.first; | ||||
qreal range = next.first - prev.first; | ||||
qreal posDelta = pos - prev.first; | ||||
qreal relativePos = posDelta / range; | ||||
//qDebug() << "range" << range << "posDelta" << posDelta << "relativePos" << relativePos; | ||||
return colorAt(prev.second, next.second, relativePos); | ||||
} | ||||
Tero Ahola
|
r103 | QTCOMMERCIALCHART_END_NAMESPACE | ||