mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
statistics: collect colors in a StatsTheme class
To enable rudimentary theming, collect all colors in a new theme class. The class has to be passed down to the various items. In general the items save a reference to the them in the constructor. Alternatively, they might also just query the StatsView everytime they need to access a color. For now, it's hard the say what is preferred: a reference per item or a function call per invokation? Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
56e02dbcc0
commit
b5aac29cea
25 changed files with 272 additions and 166 deletions
|
@ -116,16 +116,17 @@ void BarSeries::BarLabel::setVisible(bool visible)
|
|||
item->setVisible(visible);
|
||||
}
|
||||
|
||||
void BarSeries::BarLabel::highlight(bool highlight, int bin_nr, int binCount, const QColor &background)
|
||||
void BarSeries::BarLabel::highlight(bool highlight, int bin_nr, int binCount, const QColor &background, const StatsTheme &theme)
|
||||
{
|
||||
// For labels that are on top of a bar, use the corresponding bar color
|
||||
// as background. Rendering on a transparent background gives ugly artifacts.
|
||||
item->setColor(highlight || isOutside ? darkLabelColor : labelColor(bin_nr, binCount),
|
||||
item->setColor(highlight || isOutside ? theme.darkLabelColor : theme.labelColor(bin_nr, binCount),
|
||||
isOutside ? Qt::transparent : background);
|
||||
}
|
||||
|
||||
void BarSeries::BarLabel::updatePosition(bool horizontal, bool center, const QRectF &rect,
|
||||
int bin_nr, int binCount, const QColor &background)
|
||||
int bin_nr, int binCount, const QColor &background,
|
||||
const StatsTheme &theme)
|
||||
{
|
||||
QSizeF itemSize = item->getRect().size();
|
||||
if (!horizontal) {
|
||||
|
@ -173,13 +174,14 @@ void BarSeries::BarLabel::updatePosition(bool horizontal, bool center, const QRe
|
|||
}
|
||||
setVisible(true);
|
||||
// If label changed from inside to outside, or vice-versa, the color might change.
|
||||
highlight(false, bin_nr, binCount, background);
|
||||
highlight(false, bin_nr, binCount, background, theme);
|
||||
}
|
||||
|
||||
BarSeries::Item::Item(BarSeries *series, double lowerBound, double upperBound,
|
||||
std::vector<SubItem> subitemsIn,
|
||||
const QString &binName, const StatsOperationResults &res, int total,
|
||||
bool horizontal, bool stacked, int binCount) :
|
||||
bool horizontal, bool stacked, int binCount,
|
||||
const StatsTheme &theme) :
|
||||
lowerBound(lowerBound),
|
||||
upperBound(upperBound),
|
||||
subitems(std::move(subitemsIn)),
|
||||
|
@ -188,30 +190,30 @@ BarSeries::Item::Item(BarSeries *series, double lowerBound, double upperBound,
|
|||
total(total)
|
||||
{
|
||||
for (SubItem &item: subitems)
|
||||
item.highlight(false, binCount);
|
||||
updatePosition(series, horizontal, stacked, binCount);
|
||||
item.highlight(false, binCount, theme);
|
||||
updatePosition(series, horizontal, stacked, binCount, theme);
|
||||
}
|
||||
|
||||
void BarSeries::Item::highlight(int subitem, bool highlight, int binCount)
|
||||
void BarSeries::Item::highlight(int subitem, bool highlight, int binCount, const StatsTheme &theme)
|
||||
{
|
||||
if (subitem < 0 || subitem >= (int)subitems.size())
|
||||
return;
|
||||
subitems[subitem].highlight(highlight, binCount);
|
||||
subitems[subitem].highlight(highlight, binCount, theme);
|
||||
}
|
||||
|
||||
// For single-bin charts, selected items are marked with a special fill and border color.
|
||||
// For multi-bin charts, they are marked by a differend border color and border width.
|
||||
void BarSeries::SubItem::highlight(bool highlight, int binCount)
|
||||
void BarSeries::SubItem::highlight(bool highlight, int binCount, const StatsTheme &theme)
|
||||
{
|
||||
fill = highlight ? highlightedColor : binColor(bin_nr, binCount);
|
||||
QColor border = highlight ? highlightedBorderColor : ::borderColor;
|
||||
fill = highlight ? theme.highlightedColor : theme.binColor(bin_nr, binCount);
|
||||
QColor border = highlight ? theme.highlightedBorderColor : theme.borderColor;
|
||||
item->setColor(fill, border);
|
||||
item->setSelected(selected);
|
||||
if (label)
|
||||
label->highlight(highlight, bin_nr, binCount, fill);
|
||||
label->highlight(highlight, bin_nr, binCount, fill, theme);
|
||||
}
|
||||
|
||||
void BarSeries::Item::updatePosition(BarSeries *series, bool horizontal, bool stacked, int binCount)
|
||||
void BarSeries::Item::updatePosition(BarSeries *series, bool horizontal, bool stacked, int binCount, const StatsTheme &theme)
|
||||
{
|
||||
if (subitems.empty())
|
||||
return;
|
||||
|
@ -230,7 +232,7 @@ void BarSeries::Item::updatePosition(BarSeries *series, bool horizontal, bool st
|
|||
for (SubItem &item: subitems) {
|
||||
int idx = stacked ? 0 : item.bin_nr;
|
||||
double center = (idx + 0.5) * fullSubWidth + from;
|
||||
item.updatePosition(series, horizontal, stacked, center - subWidth / 2.0, center + subWidth / 2.0, binCount);
|
||||
item.updatePosition(series, horizontal, stacked, center - subWidth / 2.0, center + subWidth / 2.0, binCount, theme);
|
||||
}
|
||||
rect = subitems[0].item->getRect();
|
||||
for (auto it = std::next(subitems.begin()); it != subitems.end(); ++it)
|
||||
|
@ -238,7 +240,8 @@ void BarSeries::Item::updatePosition(BarSeries *series, bool horizontal, bool st
|
|||
}
|
||||
|
||||
void BarSeries::SubItem::updatePosition(BarSeries *series, bool horizontal, bool stacked,
|
||||
double from, double to, int binCount)
|
||||
double from, double to, int binCount,
|
||||
const StatsTheme &theme)
|
||||
{
|
||||
QPointF topLeft, bottomRight;
|
||||
if (horizontal) {
|
||||
|
@ -251,7 +254,7 @@ void BarSeries::SubItem::updatePosition(BarSeries *series, bool horizontal, bool
|
|||
QRectF rect(topLeft, bottomRight);
|
||||
item->setRect(rect);
|
||||
if (label)
|
||||
label->updatePosition(horizontal, stacked, rect, bin_nr, binCount, fill);
|
||||
label->updatePosition(horizontal, stacked, rect, bin_nr, binCount, fill, theme);
|
||||
}
|
||||
|
||||
std::vector<BarSeries::SubItem> BarSeries::makeSubItems(std::vector<SubItemDesc> items) const
|
||||
|
@ -294,13 +297,13 @@ void BarSeries::add_item(double lowerBound, double upperBound, std::vector<SubIt
|
|||
if (subitems.empty())
|
||||
return;
|
||||
items.emplace_back(this, lowerBound, upperBound, std::move(subitems), binName, res,
|
||||
total, horizontal, stacked, binCount());
|
||||
total, horizontal, stacked, binCount(), theme);
|
||||
}
|
||||
|
||||
void BarSeries::updatePositions()
|
||||
{
|
||||
for (Item &item: items)
|
||||
item.updatePosition(this, horizontal, stacked, binCount());
|
||||
item.updatePosition(this, horizontal, stacked, binCount(), theme);
|
||||
}
|
||||
|
||||
// Attention: this supposes that items are sorted by position and no bar is inside another bar!
|
||||
|
@ -407,7 +410,7 @@ bool BarSeries::hover(QPointF pos)
|
|||
// Highlight new item (if any)
|
||||
if (highlighted.bar >= 0 && highlighted.bar < (int)items.size()) {
|
||||
Item &item = items[highlighted.bar];
|
||||
item.highlight(index.subitem, true, binCount());
|
||||
item.highlight(index.subitem, true, binCount(), theme);
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information->setText(makeInfo(item, highlighted.subitem), pos);
|
||||
|
@ -422,7 +425,7 @@ bool BarSeries::hover(QPointF pos)
|
|||
void BarSeries::unhighlight()
|
||||
{
|
||||
if (highlighted.bar >= 0 && highlighted.bar < (int)items.size())
|
||||
items[highlighted.bar].highlight(highlighted.subitem, false, binCount());
|
||||
items[highlighted.bar].highlight(highlighted.subitem, false, binCount(), theme);
|
||||
highlighted = Index();
|
||||
}
|
||||
|
||||
|
@ -464,7 +467,7 @@ void BarSeries::divesSelected(const QVector<dive *> &)
|
|||
|
||||
Index idx(&item - &items[0], &subitem - &item.subitems[0]);
|
||||
bool highlight = idx == highlighted;
|
||||
item.highlight(idx.subitem, highlight, binCount());
|
||||
item.highlight(idx.subitem, highlight, binCount(), theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,8 +95,9 @@ private:
|
|||
bool isOutside; // Is shown outside of bar
|
||||
BarLabel(StatsView &view, const std::vector<QString> &labels, int bin_nr, int binCount);
|
||||
void setVisible(bool visible);
|
||||
void updatePosition(bool horizontal, bool center, const QRectF &rect, int bin_nr, int binCount, const QColor &background);
|
||||
void highlight(bool highlight, int bin_nr, int binCount, const QColor &background);
|
||||
void updatePosition(bool horizontal, bool center, const QRectF &rect, int bin_nr, int binCount,
|
||||
const QColor &background, const StatsTheme &theme);
|
||||
void highlight(bool highlight, int bin_nr, int binCount, const QColor &background, const StatsTheme &theme);
|
||||
};
|
||||
|
||||
struct SubItem {
|
||||
|
@ -109,8 +110,8 @@ private:
|
|||
bool selected;
|
||||
QColor fill;
|
||||
void updatePosition(BarSeries *series, bool horizontal, bool stacked,
|
||||
double from, double to, int binCount);
|
||||
void highlight(bool highlight, int binCount);
|
||||
double from, double to, int binCount, const StatsTheme &theme);
|
||||
void highlight(bool highlight, int binCount, const StatsTheme &theme);
|
||||
};
|
||||
|
||||
struct Item {
|
||||
|
@ -123,9 +124,11 @@ private:
|
|||
Item(BarSeries *series, double lowerBound, double upperBound,
|
||||
std::vector<SubItem> subitems,
|
||||
const QString &binName, const StatsOperationResults &res, int total, bool horizontal,
|
||||
bool stacked, int binCount);
|
||||
void updatePosition(BarSeries *series, bool horizontal, bool stacked, int binCount);
|
||||
void highlight(int subitem, bool highlight, int binCount);
|
||||
bool stacked, int binCount,
|
||||
const StatsTheme &theme);
|
||||
void updatePosition(BarSeries *series, bool horizontal, bool stacked, int binCount,
|
||||
const StatsTheme &theme);
|
||||
void highlight(int subitem, bool highlight, int binCount, const StatsTheme &theme);
|
||||
int getSubItemUnderMouse(const QPointF &f, bool horizontal, bool stacked) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -27,13 +27,13 @@ BoxSeries::~BoxSeries()
|
|||
}
|
||||
|
||||
BoxSeries::Item::Item(StatsView &view, BoxSeries *series, double lowerBound, double upperBound,
|
||||
const StatsQuartiles &qIn, const QString &binName) :
|
||||
const StatsQuartiles &qIn, const QString &binName, const StatsTheme &theme) :
|
||||
lowerBound(lowerBound), upperBound(upperBound), q(qIn),
|
||||
binName(binName),
|
||||
selected(allDivesSelected(q.dives))
|
||||
{
|
||||
item = view.createChartItem<ChartBoxItem>(ChartZValue::Series, boxBorderWidth);
|
||||
highlight(false);
|
||||
highlight(false, theme);
|
||||
updatePosition(series);
|
||||
}
|
||||
|
||||
|
@ -41,14 +41,14 @@ BoxSeries::Item::~Item()
|
|||
{
|
||||
}
|
||||
|
||||
void BoxSeries::Item::highlight(bool highlight)
|
||||
void BoxSeries::Item::highlight(bool highlight, const StatsTheme &theme)
|
||||
{
|
||||
if (highlight)
|
||||
item->setColor(highlightedColor, highlightedBorderColor);
|
||||
item->setColor(theme.highlightedColor, theme.highlightedBorderColor);
|
||||
else if (selected)
|
||||
item->setColor(selectedColor, selectedBorderColor);
|
||||
item->setColor(theme.selectedColor, theme.selectedBorderColor);
|
||||
else
|
||||
item->setColor(fillColor, ::borderColor);
|
||||
item->setColor(theme.fillColor, theme.borderColor);
|
||||
}
|
||||
|
||||
void BoxSeries::Item::updatePosition(BoxSeries *series)
|
||||
|
@ -72,7 +72,7 @@ void BoxSeries::Item::updatePosition(BoxSeries *series)
|
|||
|
||||
void BoxSeries::append(double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName)
|
||||
{
|
||||
items.emplace_back(new Item(view, this, lowerBound, upperBound, q, binName));
|
||||
items.emplace_back(new Item(view, this, lowerBound, upperBound, q, binName, theme));
|
||||
}
|
||||
|
||||
void BoxSeries::updatePositions()
|
||||
|
@ -128,7 +128,7 @@ bool BoxSeries::hover(QPointF pos)
|
|||
// Highlight new item (if any)
|
||||
if (highlighted >= 0 && highlighted < (int)items.size()) {
|
||||
Item &item = *items[highlighted];
|
||||
item.highlight(true);
|
||||
item.highlight(true, theme);
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information->setText(formatInformation(item), pos);
|
||||
|
@ -142,7 +142,7 @@ bool BoxSeries::hover(QPointF pos)
|
|||
void BoxSeries::unhighlight()
|
||||
{
|
||||
if (highlighted >= 0 && highlighted < (int)items.size())
|
||||
items[highlighted]->highlight(false);
|
||||
items[highlighted]->highlight(false, theme);
|
||||
highlighted = -1;
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ void BoxSeries::divesSelected(const QVector<dive *> &)
|
|||
|
||||
int idx = &item - &items[0];
|
||||
bool highlight = idx == highlighted;
|
||||
item->highlight(highlight);
|
||||
item->highlight(highlight, theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,10 +40,11 @@ private:
|
|||
StatsQuartiles q;
|
||||
QString binName;
|
||||
bool selected;
|
||||
Item(StatsView &view, BoxSeries *series, double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName);
|
||||
Item(StatsView &view, BoxSeries *series, double lowerBound, double upperBound, const StatsQuartiles &q,
|
||||
const QString &binName, const StatsTheme &theme);
|
||||
~Item();
|
||||
void updatePosition(BoxSeries *series);
|
||||
void highlight(bool highlight);
|
||||
void highlight(bool highlight, const StatsTheme &theme);
|
||||
};
|
||||
|
||||
QString variable, unit;
|
||||
|
|
|
@ -64,7 +64,7 @@ void ChartPixmapItem::setPositionDirty()
|
|||
markDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::render()
|
||||
void ChartPixmapItem::render(const StatsTheme &)
|
||||
{
|
||||
if (!node) {
|
||||
createNode(view.w()->createImageNode());
|
||||
|
@ -137,30 +137,25 @@ static QSGTexture *createScatterTexture(StatsView &view, const QColor &color, co
|
|||
return view.w()->createTextureFromImage(img, QQuickWindow::TextureHasAlphaChannel);
|
||||
}
|
||||
|
||||
static QSGTexture *scatterItemTexture = nullptr;
|
||||
static QSGTexture *scatterItemSelectedTexture = nullptr;
|
||||
static QSGTexture *scatterItemHighlightedTexture = nullptr;
|
||||
static QSGTexture *selectedTexture = nullptr; // A checkerboard pattern.
|
||||
|
||||
QSGTexture *ChartScatterItem::getTexture() const
|
||||
QSGTexture *ChartScatterItem::getTexture(const StatsTheme &theme) const
|
||||
{
|
||||
switch (highlight) {
|
||||
default:
|
||||
case Highlight::Unselected:
|
||||
return scatterItemTexture;
|
||||
return theme.scatterItemTexture;
|
||||
case Highlight::Selected:
|
||||
return scatterItemSelectedTexture;
|
||||
return theme.scatterItemSelectedTexture;
|
||||
case Highlight::Highlighted:
|
||||
return scatterItemHighlightedTexture;
|
||||
return theme.scatterItemHighlightedTexture;
|
||||
}
|
||||
}
|
||||
|
||||
void ChartScatterItem::render()
|
||||
void ChartScatterItem::render(const StatsTheme &theme)
|
||||
{
|
||||
if (!scatterItemTexture) {
|
||||
scatterItemTexture = register_global(createScatterTexture(view, fillColor, borderColor));
|
||||
scatterItemSelectedTexture = register_global(createScatterTexture(view, selectedColor, selectedBorderColor));
|
||||
scatterItemHighlightedTexture = register_global(createScatterTexture(view, highlightedColor, highlightedBorderColor));
|
||||
if (!theme.scatterItemTexture) {
|
||||
theme.scatterItemTexture = register_global(createScatterTexture(view, theme.fillColor, theme.borderColor));
|
||||
theme.scatterItemSelectedTexture = register_global(createScatterTexture(view, theme.selectedColor, theme.selectedBorderColor));
|
||||
theme.scatterItemHighlightedTexture = register_global(createScatterTexture(view, theme.highlightedColor, theme.highlightedBorderColor));
|
||||
}
|
||||
if (!node) {
|
||||
createNode(view.w()->createImageNode());
|
||||
|
@ -169,7 +164,7 @@ void ChartScatterItem::render()
|
|||
}
|
||||
updateVisible();
|
||||
if (textureDirty) {
|
||||
node->node->setTexture(getTexture());
|
||||
node->node->setTexture(getTexture(theme));
|
||||
textureDirty = false;
|
||||
}
|
||||
if (positionDirty) {
|
||||
|
@ -288,7 +283,7 @@ ChartPieItem::ChartPieItem(StatsView &v, ChartZValue z, double borderWidth) : Ch
|
|||
{
|
||||
}
|
||||
|
||||
static QBrush makeBrush(QColor fill, bool selected)
|
||||
static QBrush makeBrush(QColor fill, bool selected, const StatsTheme &theme)
|
||||
{
|
||||
if (!selected)
|
||||
return QBrush(fill);
|
||||
|
@ -296,18 +291,18 @@ static QBrush makeBrush(QColor fill, bool selected)
|
|||
img.fill(fill);
|
||||
for (int x = 0; x < selectionOverlayPixelSize; ++x) {
|
||||
for (int y = 0; y < selectionOverlayPixelSize; ++y) {
|
||||
img.setPixelColor(x, y, selectionOverlayColor);
|
||||
img.setPixelColor(x, y, theme.selectionOverlayColor);
|
||||
img.setPixelColor(x + selectionOverlayPixelSize, y + selectionOverlayPixelSize,
|
||||
selectionOverlayColor);
|
||||
theme.selectionOverlayColor);
|
||||
}
|
||||
}
|
||||
return QBrush(img);
|
||||
}
|
||||
|
||||
void ChartPieItem::drawSegment(double from, double to, QColor fill, QColor border, bool selected)
|
||||
void ChartPieItem::drawSegment(double from, double to, QColor fill, QColor border, bool selected, const StatsTheme &theme)
|
||||
{
|
||||
painter->setPen(QPen(border, borderWidth));
|
||||
painter->setBrush(makeBrush(fill, selected));
|
||||
painter->setBrush(makeBrush(fill, selected, theme));
|
||||
// For whatever obscure reason, angles of pie pieces are given as 16th of a degree...?
|
||||
// Angles increase CCW, whereas pie charts usually are read CW. Therfore, startAngle
|
||||
// is dervied from "from" and subtracted from the origin angle at 12:00.
|
||||
|
@ -353,7 +348,7 @@ void setPoint(QSGGeometry::TexturedPoint2D &v, const QPointF &p, const QPointF &
|
|||
static_cast<float>(t.x()), static_cast<float>(t.y()));
|
||||
}
|
||||
|
||||
void ChartLineItem::render()
|
||||
void ChartLineItem::render(const StatsTheme &)
|
||||
{
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2));
|
||||
|
@ -384,7 +379,7 @@ void ChartLineItem::render()
|
|||
positionDirty = materialDirty = false;
|
||||
}
|
||||
|
||||
void ChartRectLineItem::render()
|
||||
void ChartRectLineItem::render(const StatsTheme &)
|
||||
{
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4));
|
||||
|
@ -427,19 +422,19 @@ ChartBarItem::~ChartBarItem()
|
|||
{
|
||||
}
|
||||
|
||||
QSGTexture *ChartBarItem::getSelectedTexture() const
|
||||
QSGTexture *ChartBarItem::getSelectedTexture(const StatsTheme &theme) const
|
||||
{
|
||||
if (!selectedTexture) {
|
||||
if (!theme.selectedTexture) {
|
||||
QImage img(2, 2, QImage::Format_ARGB32);
|
||||
img.fill(Qt::transparent);
|
||||
img.setPixelColor(0, 0, selectionOverlayColor);
|
||||
img.setPixelColor(1, 1, selectionOverlayColor);
|
||||
selectedTexture = register_global(view.w()->createTextureFromImage(img, QQuickWindow::TextureHasAlphaChannel));
|
||||
img.setPixelColor(0, 0, theme.selectionOverlayColor);
|
||||
img.setPixelColor(1, 1, theme.selectionOverlayColor);
|
||||
theme.selectedTexture = register_global(view.w()->createTextureFromImage(img, QQuickWindow::TextureHasAlphaChannel));
|
||||
}
|
||||
return selectedTexture;
|
||||
return theme.selectedTexture;
|
||||
}
|
||||
|
||||
void ChartBarItem::render()
|
||||
void ChartBarItem::render(const StatsTheme &theme)
|
||||
{
|
||||
if (!node) {
|
||||
createNode(view.w()->createRectangleNode());
|
||||
|
@ -483,7 +478,7 @@ void ChartBarItem::render()
|
|||
selectionGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4));
|
||||
selectionGeometry->setDrawingMode(QSGGeometry::DrawTriangleFan);
|
||||
selectionMaterial.reset(new QSGTextureMaterial);
|
||||
selectionMaterial->setTexture(getSelectedTexture());
|
||||
selectionMaterial->setTexture(getSelectedTexture(theme));
|
||||
selectionMaterial->setHorizontalWrapMode(QSGTexture::Repeat);
|
||||
selectionMaterial->setVerticalWrapMode(QSGTexture::Repeat);
|
||||
selectionNode.reset(new QSGGeometryNode);
|
||||
|
@ -556,12 +551,12 @@ ChartBoxItem::~ChartBoxItem()
|
|||
{
|
||||
}
|
||||
|
||||
void ChartBoxItem::render()
|
||||
void ChartBoxItem::render(const StatsTheme &theme)
|
||||
{
|
||||
// Remember old dirty values, since ChartBarItem::render() will clear them
|
||||
bool oldPositionDirty = positionDirty;
|
||||
bool oldColorDirty = colorDirty;
|
||||
ChartBarItem::render(); // This will create the base node, so no need to check for that.
|
||||
ChartBarItem::render(theme); // This will create the base node, so no need to check for that.
|
||||
if (!whiskersNode) {
|
||||
whiskersGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 10));
|
||||
whiskersGeometry->setDrawingMode(QSGGeometry::DrawLines);
|
||||
|
|
|
@ -16,12 +16,14 @@ class QSGImageNode;
|
|||
class QSGRectangleNode;
|
||||
class QSGTexture;
|
||||
class QSGTextureMaterial;
|
||||
class StatsTheme;
|
||||
class StatsView;
|
||||
enum class ChartZValue : int;
|
||||
|
||||
class ChartItem {
|
||||
public:
|
||||
virtual void render() = 0; // Only call on render thread!
|
||||
// Only call on render thread!
|
||||
virtual void render(const StatsTheme &theme) = 0;
|
||||
bool dirty; // If true, call render() when rebuilding the scene
|
||||
ChartItem *prev, *next; // Double linked list of items
|
||||
const ChartZValue zValue;
|
||||
|
@ -58,7 +60,7 @@ public:
|
|||
~ChartPixmapItem();
|
||||
|
||||
void setPos(QPointF pos);
|
||||
void render() override; // Only call on render thread!
|
||||
void render(const StatsTheme &theme) override;
|
||||
QRectF getRect() const;
|
||||
protected:
|
||||
void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*.
|
||||
|
@ -107,7 +109,7 @@ private:
|
|||
class ChartPieItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartPieItem(StatsView &v, ChartZValue z, double borderWidth);
|
||||
void drawSegment(double from, double to, QColor fill, QColor border, bool selected); // from and to are relative (0-1 is full disk).
|
||||
void drawSegment(double from, double to, QColor fill, QColor border, bool selected, const StatsTheme &theme); // from and to are relative (0-1 is full disk).
|
||||
void resize(QSizeF size); // As in base class, but clears the canvas
|
||||
private:
|
||||
double borderWidth;
|
||||
|
@ -132,14 +134,14 @@ protected:
|
|||
class ChartLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render() override; // Only call on render thread!
|
||||
void render(const StatsTheme &theme) override;
|
||||
};
|
||||
|
||||
// A simple rectangle without fill. Specified by any two opposing vertices.
|
||||
class ChartRectLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render() override; // Only call on render thread!
|
||||
void render(const StatsTheme &theme) override;
|
||||
};
|
||||
|
||||
// A bar in a bar chart: a rectangle bordered by lines.
|
||||
|
@ -151,7 +153,7 @@ public:
|
|||
void setRect(const QRectF &rect);
|
||||
void setSelected(bool selected);
|
||||
QRectF getRect() const;
|
||||
void render() override; // Only call on render thread!
|
||||
void render(const StatsTheme &theme) override;
|
||||
protected:
|
||||
QColor color, borderColor;
|
||||
double borderWidth;
|
||||
|
@ -168,7 +170,7 @@ private:
|
|||
std::unique_ptr<QSGGeometryNode> selectionNode;
|
||||
std::unique_ptr<QSGTextureMaterial> selectionMaterial;
|
||||
std::unique_ptr<QSGGeometry> selectionGeometry;
|
||||
QSGTexture *getSelectedTexture() const;
|
||||
QSGTexture *getSelectedTexture(const StatsTheme &theme) const;
|
||||
};
|
||||
|
||||
// A box-and-whiskers item. This is a bit lazy: derive from the bar item and add whiskers.
|
||||
|
@ -178,7 +180,7 @@ public:
|
|||
~ChartBoxItem();
|
||||
void setBox(const QRectF &rect, double min, double max, double median); // The rect describes Q1, Q3.
|
||||
QRectF getRect() const; // Note: this extends the center rectangle to include the whiskers.
|
||||
void render() override; // Only call on render thread!
|
||||
void render(const StatsTheme &theme) override;
|
||||
private:
|
||||
double min, max, median;
|
||||
std::unique_ptr<QSGGeometryNode> whiskersNode;
|
||||
|
@ -203,12 +205,12 @@ public:
|
|||
};
|
||||
void setPos(QPointF pos); // Specifies the *center* of the item.
|
||||
void setHighlight(Highlight highlight); // In the future, support different kinds of scatter items.
|
||||
void render() override; // Only call on render thread!
|
||||
void render(const StatsTheme &theme) override;
|
||||
QRectF getRect() const;
|
||||
bool contains(QPointF point) const;
|
||||
bool inRect(const QRectF &rect) const;
|
||||
private:
|
||||
QSGTexture *getTexture() const;
|
||||
QSGTexture *getTexture(const StatsTheme &theme) const;
|
||||
QRectF rect;
|
||||
QSizeF textureSize;
|
||||
bool positionDirty, textureDirty;
|
||||
|
|
|
@ -11,8 +11,9 @@ static const int distanceFromPointer = 10; // Distance to place box from mouse p
|
|||
|
||||
InformationBox::InformationBox(StatsView &v) :
|
||||
ChartRectItem(v, ChartZValue::InformationBox,
|
||||
QPen(informationBorderColor, informationBorder),
|
||||
QBrush(informationColor), informationBorderRadius),
|
||||
QPen(v.getCurrentTheme().informationBorderColor, informationBorder),
|
||||
QBrush(v.getCurrentTheme().informationColor), informationBorderRadius),
|
||||
theme(v.getCurrentTheme()),
|
||||
width(0.0),
|
||||
height(0.0)
|
||||
{
|
||||
|
@ -36,7 +37,7 @@ void InformationBox::setText(const std::vector<QString> &text, QPointF pos)
|
|||
|
||||
ChartRectItem::resize(QSizeF(width, height));
|
||||
|
||||
painter->setPen(QPen(darkLabelColor)); // QPainter uses QPen to set text color!
|
||||
painter->setPen(QPen(theme.darkLabelColor)); // QPainter uses QPen to set text color!
|
||||
double y = 2.0 * informationBorder;
|
||||
for (size_t i = 0; i < widths.size(); ++i) {
|
||||
QRectF rect(2.0 * informationBorder, y, widths[i], fontHeight);
|
||||
|
|
|
@ -20,6 +20,7 @@ struct InformationBox : ChartRectItem {
|
|||
void setPos(QPointF pos);
|
||||
int recommendedMaxLines() const;
|
||||
private:
|
||||
const StatsTheme &theme; // Set once in constructor.
|
||||
QFont font; // For future specialization.
|
||||
double width, height;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "legend.h"
|
||||
#include "statscolors.h"
|
||||
#include "statsview.h"
|
||||
#include "zvalues.h"
|
||||
|
||||
#include <cmath>
|
||||
|
@ -15,8 +16,10 @@ static const double legendInternalBorderSize = 2.0;
|
|||
|
||||
Legend::Legend(StatsView &view, const std::vector<QString> &names) :
|
||||
ChartRectItem(view, ChartZValue::Legend,
|
||||
QPen(legendBorderColor, legendBorderSize), QBrush(legendColor), legendBoxBorderRadius),
|
||||
QPen(view.getCurrentTheme().legendBorderColor, legendBorderSize),
|
||||
QBrush(view.getCurrentTheme().legendColor), legendBoxBorderRadius),
|
||||
displayedItems(0), width(0.0), height(0.0),
|
||||
theme(view.getCurrentTheme()),
|
||||
font(QFont()), // Make configurable
|
||||
posInitialized(false)
|
||||
{
|
||||
|
@ -25,12 +28,12 @@ Legend::Legend(StatsView &view, const std::vector<QString> &names) :
|
|||
fontHeight = fm.height();
|
||||
int idx = 0;
|
||||
for (const QString &name: names)
|
||||
entries.emplace_back(name, idx++, (int)names.size(), fm);
|
||||
entries.emplace_back(name, idx++, (int)names.size(), fm, theme);
|
||||
}
|
||||
|
||||
Legend::Entry::Entry(const QString &name, int idx, int numBins, const QFontMetrics &fm) :
|
||||
Legend::Entry::Entry(const QString &name, int idx, int numBins, const QFontMetrics &fm, const StatsTheme &theme) :
|
||||
name(name),
|
||||
rectBrush(QBrush(binColor(idx, numBins)))
|
||||
rectBrush(QBrush(theme.binColor(idx, numBins)))
|
||||
{
|
||||
width = fm.height() + 2.0 * legendBoxBorderSize + fm.size(Qt::TextSingleLine, name).width();
|
||||
}
|
||||
|
@ -82,7 +85,7 @@ void Legend::resize()
|
|||
ChartRectItem::resize(QSizeF(width, height));
|
||||
|
||||
// Paint rectangles
|
||||
painter->setPen(QPen(legendBorderColor, legendBoxBorderSize));
|
||||
painter->setPen(QPen(theme.legendBorderColor, legendBoxBorderSize));
|
||||
for (int i = 0; i < displayedItems; ++i) {
|
||||
QPointF itemPos = entries[i].pos;
|
||||
painter->setBrush(entries[i].rectBrush);
|
||||
|
@ -94,7 +97,7 @@ void Legend::resize()
|
|||
}
|
||||
|
||||
// Paint labels
|
||||
painter->setPen(darkLabelColor); // QPainter uses pen not brush for text!
|
||||
painter->setPen(theme.darkLabelColor); // QPainter uses pen not brush for text!
|
||||
painter->setFont(font);
|
||||
for (int i = 0; i < displayedItems; ++i) {
|
||||
QPointF itemPos = entries[i].pos;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <QFont>
|
||||
|
||||
class QFontMetrics;
|
||||
class StatsTheme;
|
||||
|
||||
class Legend : public ChartRectItem {
|
||||
public:
|
||||
|
@ -23,11 +24,12 @@ private:
|
|||
QBrush rectBrush;
|
||||
QPointF pos;
|
||||
double width;
|
||||
Entry(const QString &name, int idx, int numBins, const QFontMetrics &fm);
|
||||
Entry(const QString &name, int idx, int numBins, const QFontMetrics &fm, const StatsTheme &);
|
||||
};
|
||||
int displayedItems;
|
||||
double width;
|
||||
double height;
|
||||
const StatsTheme &theme; // Set once in constructor.
|
||||
QFont font;
|
||||
// The position is specified with respect to the center and in relative terms
|
||||
// with respect to the canvas.
|
||||
|
|
|
@ -18,7 +18,7 @@ static const double innerLabelRadius = 0.75; // 1.0 = at outer border of pie
|
|||
static const double outerLabelRadius = 1.01; // 1.0 = at outer border of pie
|
||||
|
||||
PieSeries::Item::Item(StatsView &view, const QString &name, int from, std::vector<dive *> divesIn, int totalCount,
|
||||
int bin_nr, int numBins) :
|
||||
int bin_nr, int numBins, const StatsTheme &theme) :
|
||||
name(name),
|
||||
dives(std::move(divesIn)),
|
||||
selected(allDivesSelected(dives))
|
||||
|
@ -43,7 +43,7 @@ PieSeries::Item::Item(StatsView &view, const QString &name, int from, std::vecto
|
|||
innerLabel = view.createChartItem<ChartTextItem>(ChartZValue::SeriesLabels, f, innerLabelText);
|
||||
|
||||
outerLabel = view.createChartItem<ChartTextItem>(ChartZValue::SeriesLabels, f, name);
|
||||
outerLabel->setColor(darkLabelColor);
|
||||
outerLabel->setColor(theme.darkLabelColor);
|
||||
}
|
||||
|
||||
void PieSeries::Item::updatePositions(const QPointF ¢er, double radius)
|
||||
|
@ -72,13 +72,13 @@ void PieSeries::Item::updatePositions(const QPointF ¢er, double radius)
|
|||
}
|
||||
}
|
||||
|
||||
void PieSeries::Item::highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins)
|
||||
void PieSeries::Item::highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins, const StatsTheme &theme)
|
||||
{
|
||||
QColor fill = highlight ? highlightedColor : binColor(bin_nr, numBins);
|
||||
QColor border = highlight ? highlightedBorderColor : ::borderColor;
|
||||
QColor fill = highlight ? theme.highlightedColor : theme.binColor(bin_nr, numBins);
|
||||
QColor border = highlight ? theme.highlightedBorderColor : theme.borderColor;
|
||||
if (innerLabel)
|
||||
innerLabel->setColor(highlight ? darkLabelColor : labelColor(bin_nr, numBins), fill);
|
||||
item.drawSegment(angleFrom, angleTo, fill, border, selected);
|
||||
innerLabel->setColor(highlight ? theme.darkLabelColor : theme.labelColor(bin_nr, numBins), fill);
|
||||
item.drawSegment(angleFrom, angleTo, fill, border, selected, theme);
|
||||
}
|
||||
|
||||
PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName,
|
||||
|
@ -147,7 +147,7 @@ PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const
|
|||
for (auto it2 = sorted.begin(); it2 != it; ++it2) {
|
||||
int count = (int)data[*it2].second.size();
|
||||
items.emplace_back(view, data[*it2].first, act, std::move(data[*it2].second),
|
||||
totalCount, (int)items.size(), numBins);
|
||||
totalCount, (int)items.size(), numBins, theme);
|
||||
act += count;
|
||||
}
|
||||
|
||||
|
@ -162,7 +162,7 @@ PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const
|
|||
otherDives.push_back(d);
|
||||
}
|
||||
QString name = StatsTranslations::tr("other (%1 items)").arg(other.size());
|
||||
items.emplace_back(view, name, act, std::move(otherDives), totalCount, (int)items.size(), numBins);
|
||||
items.emplace_back(view, name, act, std::move(otherDives), totalCount, (int)items.size(), numBins, theme);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ void PieSeries::updatePositions()
|
|||
int i = 0;
|
||||
for (Item &segment: items) {
|
||||
segment.updatePositions(center, radius);
|
||||
segment.highlight(*item, i, i == highlighted, (int)items.size()); // Draw segment
|
||||
segment.highlight(*item, i, i == highlighted, (int)items.size(), theme); // Draw segment
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ bool PieSeries::hover(QPointF pos)
|
|||
|
||||
// Highlight new item (if any)
|
||||
if (highlighted >= 0 && highlighted < (int)items.size()) {
|
||||
items[highlighted].highlight(*item, highlighted, true, (int)items.size());
|
||||
items[highlighted].highlight(*item, highlighted, true, (int)items.size(), theme);
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information->setText(makeInfo(highlighted), pos);
|
||||
|
@ -269,7 +269,7 @@ bool PieSeries::hover(QPointF pos)
|
|||
void PieSeries::unhighlight()
|
||||
{
|
||||
if (highlighted >= 0 && highlighted < (int)items.size())
|
||||
items[highlighted].highlight(*item, highlighted, false, (int)items.size());
|
||||
items[highlighted].highlight(*item, highlighted, false, (int)items.size(), theme);
|
||||
highlighted = -1;
|
||||
}
|
||||
|
||||
|
@ -314,7 +314,7 @@ void PieSeries::divesSelected(const QVector<dive *> &)
|
|||
segment.selected = selected;
|
||||
|
||||
int idx = &segment - &items[0];
|
||||
segment.highlight(*item, idx, idx == highlighted, (int)items.size());
|
||||
segment.highlight(*item, idx, idx == highlighted, (int)items.size(), theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,9 +49,9 @@ private:
|
|||
QPointF innerLabelPos, outerLabelPos; // With respect to a (-1, -1)-(1, 1) rectangle.
|
||||
bool selected;
|
||||
Item(StatsView &view, const QString &name, int from, std::vector<dive *> dives, int totalCount,
|
||||
int bin_nr, int numBins);
|
||||
int bin_nr, int numBins, const StatsTheme &theme);
|
||||
void updatePositions(const QPointF ¢er, double radius);
|
||||
void highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins);
|
||||
void highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins, const StatsTheme &theme);
|
||||
};
|
||||
std::vector<Item> items;
|
||||
int totalCount;
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "quartilemarker.h"
|
||||
#include "statscolors.h"
|
||||
#include "statsaxis.h"
|
||||
#include "statscolors.h"
|
||||
#include "statsview.h"
|
||||
#include "zvalues.h"
|
||||
|
||||
static const double quartileMarkerSize = 15.0;
|
||||
|
||||
QuartileMarker::QuartileMarker(StatsView &view, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartLineItem(view, ChartZValue::ChartFeatures, quartileMarkerColor, 2.0),
|
||||
ChartLineItem(view, ChartZValue::ChartFeatures, view.getCurrentTheme().quartileMarkerColor, 2.0),
|
||||
xAxis(xAxis), yAxis(yAxis),
|
||||
pos(pos),
|
||||
value(value)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "regressionitem.h"
|
||||
#include "statsaxis.h"
|
||||
#include "statscolors.h"
|
||||
#include "statsview.h"
|
||||
#include "zvalues.h"
|
||||
|
||||
#include <cmath>
|
||||
|
@ -11,6 +12,7 @@ static const double regressionLineWidth = 2.0;
|
|||
RegressionItem::RegressionItem(StatsView &view, regression_data reg,
|
||||
StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartPixmapItem(view, ChartZValue::ChartFeatures),
|
||||
theme(view.getCurrentTheme()),
|
||||
xAxis(xAxis), yAxis(yAxis), reg(reg),
|
||||
regression(true), confidence(true)
|
||||
{
|
||||
|
@ -86,7 +88,7 @@ void RegressionItem::updatePosition()
|
|||
|
||||
img->fill(Qt::transparent);
|
||||
if (confidence) {
|
||||
QColor col(regressionItemColor);
|
||||
QColor col(theme.regressionItemColor);
|
||||
col.setAlphaF((float)reg.r2);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(QBrush(col));
|
||||
|
@ -94,7 +96,7 @@ void RegressionItem::updatePosition()
|
|||
}
|
||||
|
||||
if (regression) {
|
||||
painter->setPen(QPen(regressionItemColor, regressionLineWidth));
|
||||
painter->setPen(QPen(theme.regressionItemColor, regressionLineWidth));
|
||||
painter->drawLine(QPointF(linePolygon[0]), QPointF(linePolygon[1]));
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "chartitem.h"
|
||||
|
||||
class StatsAxis;
|
||||
class StatsTheme;
|
||||
class StatsView;
|
||||
|
||||
struct regression_data {
|
||||
|
@ -20,6 +21,7 @@ public:
|
|||
void updatePosition();
|
||||
void setFeatures(bool regression, bool confidence);
|
||||
private:
|
||||
const StatsTheme &theme; // Initialized once in constructor
|
||||
StatsAxis *xAxis, *yAxis;
|
||||
regression_data reg;
|
||||
bool regression, confidence;
|
||||
|
|
|
@ -26,7 +26,8 @@ static const double axisTitleSpaceVertical = 2.0; // Space between labels and ti
|
|||
|
||||
StatsAxis::StatsAxis(StatsView &view, const QString &title, bool horizontal, bool labelsBetweenTicks) :
|
||||
ChartPixmapItem(view, ChartZValue::Axes),
|
||||
line(view.createChartItem<ChartLineItem>(ChartZValue::Axes, axisColor, axisWidth)),
|
||||
theme(view.getCurrentTheme()),
|
||||
line(view.createChartItem<ChartLineItem>(ChartZValue::Axes, theme.axisColor, axisWidth)),
|
||||
title(title), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
|
||||
size(1.0), zeroOnScreen(0.0), min(0.0), max(1.0), labelWidth(0.0)
|
||||
{
|
||||
|
@ -128,7 +129,7 @@ void StatsAxis::addLabel(const QFontMetrics &fm, const QString &label, double po
|
|||
|
||||
void StatsAxis::addTick(double pos)
|
||||
{
|
||||
ticks.push_back({ view.createChartItem<ChartLineItem>(ChartZValue::Axes, axisColor, axisTickWidth), pos });
|
||||
ticks.push_back({ view.createChartItem<ChartLineItem>(ChartZValue::Axes, theme.axisColor, axisTickWidth), pos });
|
||||
}
|
||||
|
||||
std::vector<double> StatsAxis::ticksPositions() const
|
||||
|
@ -190,7 +191,7 @@ void StatsAxis::setSize(double sizeIn)
|
|||
offset = QPointF(round(offsetX), round(offsetY));
|
||||
img->fill(Qt::transparent);
|
||||
|
||||
painter->setPen(QPen(darkLabelColor));
|
||||
painter->setPen(QPen(theme.darkLabelColor));
|
||||
painter->setFont(labelFont);
|
||||
for (const Label &label: labels) {
|
||||
double x = (label.pos - min) / (max - min) * size + offset.x() - round(label.width / 2.0);
|
||||
|
@ -219,7 +220,7 @@ void StatsAxis::setSize(double sizeIn)
|
|||
offset = QPointF(round(offsetX), round(offsetY));
|
||||
img->fill(Qt::transparent);
|
||||
|
||||
painter->setPen(QPen(darkLabelColor));
|
||||
painter->setPen(QPen(theme.darkLabelColor));
|
||||
painter->setFont(labelFont);
|
||||
for (const Label &label: labels) {
|
||||
double y = (min - label.pos) / (max - min) * size + offset.y() - round(fontHeight / 2.0);
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
protected:
|
||||
StatsAxis(StatsView &view, const QString &title, bool horizontal, bool labelsBetweenTicks);
|
||||
|
||||
const StatsTheme &theme; // Initialized once in constructor.
|
||||
ChartItemPtr<ChartLineItem> line;
|
||||
QString title;
|
||||
double titleWidth;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "statscolors.h"
|
||||
#include "statstranslations.h"
|
||||
|
||||
// Colors created using the Chroma.js Color Palette Helper
|
||||
// https://vis4.net/palettes/#/50|d|00108c,3ed8ff,ffffe0|ffffe0,ff005e,743535|1|1
|
||||
|
@ -15,22 +16,65 @@ static const QColor binColors[] = {
|
|||
QRgb(0xa83e49), QRgb(0x9d3a44), QRgb(0x90383f), QRgb(0x83363a), QRgb(0x743535)
|
||||
};
|
||||
|
||||
// Pick roughly equidistant colors out of the color set above
|
||||
// if we need more bins than we have colors (what chart is THAT?) simply loop
|
||||
QColor binColor(int bin, int numBins)
|
||||
StatsTheme::StatsTheme() :
|
||||
scatterItemTexture(nullptr),
|
||||
scatterItemSelectedTexture(nullptr),
|
||||
scatterItemHighlightedTexture(nullptr),
|
||||
selectedTexture(nullptr)
|
||||
{
|
||||
if (numBins == 1 || bin < 0 || bin >= numBins)
|
||||
return fillColor;
|
||||
if (numBins > (int)std::size(binColors))
|
||||
return binColors[bin % std::size(binColors)];
|
||||
|
||||
// use integer math to spread out the indices
|
||||
int idx = bin * (std::size(binColors) - 1) / (numBins - 1);
|
||||
return binColors[idx];
|
||||
}
|
||||
|
||||
// Figure out if we want a light or a dark label
|
||||
QColor labelColor(int bin, size_t numBins)
|
||||
{
|
||||
return (binColor(bin, numBins).lightness() < 150) ? lightLabelColor : darkLabelColor;
|
||||
}
|
||||
class StatsThemeLight : public StatsTheme {
|
||||
public:
|
||||
StatsThemeLight()
|
||||
{
|
||||
backgroundColor = Qt::white;
|
||||
fillColor = QColor(0x44, 0x76, 0xaa);
|
||||
borderColor = QColor(0x66, 0xb2, 0xff);
|
||||
selectedColor = QColor(0xaa, 0x76, 0x44);
|
||||
selectedBorderColor = QColor(0xff, 0xb2, 0x66);
|
||||
highlightedColor = Qt::yellow;
|
||||
highlightedBorderColor = QColor(0xaa, 0xaa, 0x22);
|
||||
darkLabelColor = Qt::black;
|
||||
lightLabelColor = Qt::white;
|
||||
axisColor = Qt::black;
|
||||
gridColor = QColor(0xcc, 0xcc, 0xcc);
|
||||
informationBorderColor = Qt::black;
|
||||
informationColor = QColor(0xff, 0xff, 0x00, 192); // Note: fourth argument is opacity
|
||||
legendColor = QColor(0x00, 0x8e, 0xcc, 192); // Note: fourth argument is opacity
|
||||
legendBorderColor = Qt::black;
|
||||
quartileMarkerColor = Qt::red;
|
||||
regressionItemColor = Qt::red;
|
||||
meanMarkerColor = Qt::green;
|
||||
medianMarkerColor = Qt::red;
|
||||
selectionLassoColor = Qt::black;
|
||||
selectionOverlayColor = Qt::lightGray;
|
||||
}
|
||||
private:
|
||||
QString name() const
|
||||
{
|
||||
return StatsTranslations::tr("Light");
|
||||
}
|
||||
|
||||
// Pick roughly equidistant colors out of the color set above
|
||||
// if we need more bins than we have colors (what chart is THAT?) simply loop
|
||||
QColor binColor(int bin, int numBins) const override
|
||||
{
|
||||
if (numBins == 1 || bin < 0 || bin >= numBins)
|
||||
return fillColor;
|
||||
if (numBins > (int)std::size(binColors))
|
||||
return binColors[bin % std::size(binColors)];
|
||||
|
||||
// use integer math to spread out the indices
|
||||
int idx = bin * (std::size(binColors) - 1) / (numBins - 1);
|
||||
return binColors[idx];
|
||||
}
|
||||
|
||||
QColor labelColor(int bin, size_t numBins) const override
|
||||
{
|
||||
return (binColor(bin, numBins).lightness() < 150) ? lightLabelColor : darkLabelColor;
|
||||
}
|
||||
};
|
||||
|
||||
static StatsThemeLight statsThemeLight;
|
||||
std::vector<const StatsTheme *> statsThemes = { &statsThemeLight };
|
||||
|
|
|
@ -3,31 +3,53 @@
|
|||
#ifndef STATSCOLORS_H
|
||||
#define STATSCOLORS_H
|
||||
|
||||
#include <vector>
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
|
||||
inline const QColor backgroundColor(Qt::white);
|
||||
inline const QColor fillColor(0x44, 0x76, 0xaa);
|
||||
inline const QColor borderColor(0x66, 0xb2, 0xff);
|
||||
inline const QColor selectedColor(0xaa, 0x76, 0x44);
|
||||
inline const QColor selectedBorderColor(0xff, 0xb2, 0x66);
|
||||
inline const QColor highlightedColor(Qt::yellow);
|
||||
inline const QColor highlightedBorderColor(0xaa, 0xaa, 0x22);
|
||||
inline const QColor darkLabelColor(Qt::black);
|
||||
inline const QColor lightLabelColor(Qt::white);
|
||||
inline const QColor axisColor(Qt::black);
|
||||
inline const QColor gridColor(0xcc, 0xcc, 0xcc);
|
||||
inline const QColor informationBorderColor(Qt::black);
|
||||
inline const QColor informationColor(0xff, 0xff, 0x00, 192); // Note: fourth argument is opacity
|
||||
inline const QColor legendColor(0x00, 0x8e, 0xcc, 192); // Note: fourth argument is opacity
|
||||
inline const QColor legendBorderColor(Qt::black);
|
||||
inline const QColor quartileMarkerColor(Qt::red);
|
||||
inline const QColor regressionItemColor(Qt::red);
|
||||
inline const QColor meanMarkerColor(Qt::green);
|
||||
inline const QColor medianMarkerColor(Qt::red);
|
||||
inline const QColor selectionLassoColor(Qt::black);
|
||||
inline const QColor selectionOverlayColor(Qt::lightGray);
|
||||
class QSGTexture;
|
||||
|
||||
QColor binColor(int bin, int numBins);
|
||||
QColor labelColor(int bin, size_t numBins);
|
||||
class StatsTheme {
|
||||
public:
|
||||
StatsTheme();
|
||||
virtual QString name() const = 0;
|
||||
QColor backgroundColor;
|
||||
QColor fillColor;
|
||||
QColor borderColor;
|
||||
QColor selectedColor;
|
||||
QColor selectedBorderColor;
|
||||
QColor highlightedColor;
|
||||
QColor highlightedBorderColor;
|
||||
// The dark and light label colors are with respect to the light theme.
|
||||
// In the dark theme, dark is light and light is dark. Might want to change the names!
|
||||
QColor darkLabelColor;
|
||||
QColor lightLabelColor;
|
||||
QColor axisColor;
|
||||
QColor gridColor;
|
||||
QColor informationBorderColor;
|
||||
QColor informationColor;
|
||||
QColor legendColor;
|
||||
QColor legendBorderColor;
|
||||
QColor quartileMarkerColor;
|
||||
QColor regressionItemColor;
|
||||
QColor meanMarkerColor;
|
||||
QColor medianMarkerColor;
|
||||
QColor selectionLassoColor;
|
||||
QColor selectionOverlayColor;
|
||||
virtual QColor binColor(int bin, int numBins) const = 0;
|
||||
virtual QColor labelColor(int bin, size_t numBins) const = 0;
|
||||
|
||||
// These are cashes for the QSG rendering engine
|
||||
// Note: Originally these were std::unique_ptrs, which automatically
|
||||
// freed the textures on exit. However, destroying textures after
|
||||
// QApplication finished its thread leads to crashes. Therefore, these
|
||||
// are now normal pointers and the texture objects are leaked.
|
||||
mutable QSGTexture *scatterItemTexture = nullptr;
|
||||
mutable QSGTexture *scatterItemSelectedTexture = nullptr;
|
||||
mutable QSGTexture *scatterItemHighlightedTexture = nullptr;
|
||||
mutable QSGTexture *selectedTexture = nullptr; // A checkerboard pattern.
|
||||
};
|
||||
|
||||
extern std::vector<const StatsTheme *> statsThemes;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
static const double gridWidth = 1.0;
|
||||
|
||||
StatsGrid::StatsGrid(StatsView &view, const StatsAxis &xAxis, const StatsAxis &yAxis)
|
||||
: view(view), xAxis(xAxis), yAxis(yAxis)
|
||||
: view(view), theme(view.getCurrentTheme()), xAxis(xAxis), yAxis(yAxis)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -28,11 +28,11 @@ void StatsGrid::updatePositions()
|
|||
return;
|
||||
|
||||
for (double x: xtics) {
|
||||
lines.push_back(view.createChartItem<ChartLineItem>(ChartZValue::Grid, gridColor, gridWidth));
|
||||
lines.push_back(view.createChartItem<ChartLineItem>(ChartZValue::Grid, theme.gridColor, gridWidth));
|
||||
lines.back()->setLine(QPointF(x, ytics.front()), QPointF(x, ytics.back()));
|
||||
}
|
||||
for (double y: ytics) {
|
||||
lines.push_back(view.createChartItem<ChartLineItem>(ChartZValue::Grid, gridColor, gridWidth));
|
||||
lines.push_back(view.createChartItem<ChartLineItem>(ChartZValue::Grid, theme.gridColor, gridWidth));
|
||||
lines.back()->setLine(QPointF(xtics.front(), y), QPointF(xtics.back(), y));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <vector>
|
||||
|
||||
class StatsAxis;
|
||||
class StatsTheme;
|
||||
class StatsView;
|
||||
class ChartLineItem;
|
||||
|
||||
|
@ -16,6 +17,7 @@ public:
|
|||
void updatePositions();
|
||||
private:
|
||||
StatsView &view;
|
||||
const StatsTheme &theme; // Initialized once in constructor.
|
||||
const StatsAxis &xAxis, &yAxis;
|
||||
std::vector<ChartItemPtr<ChartLineItem>> lines;
|
||||
};
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "statsseries.h"
|
||||
#include "statsaxis.h"
|
||||
#include "statsview.h"
|
||||
|
||||
StatsSeries::StatsSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
view(view), xAxis(xAxis), yAxis(yAxis)
|
||||
view(view), theme(view.getCurrentTheme()), xAxis(xAxis), yAxis(yAxis)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <QPointF>
|
||||
|
||||
class StatsAxis;
|
||||
class StatsTheme;
|
||||
class StatsView;
|
||||
struct dive;
|
||||
class QRectF;
|
||||
|
@ -30,6 +31,7 @@ public:
|
|||
|
||||
protected:
|
||||
StatsView &view;
|
||||
const StatsTheme &theme; // Theme is only set once in the constructor
|
||||
StatsAxis *xAxis, *yAxis; // May be zero for charts without axes (pie charts).
|
||||
QPointF toScreen(QPointF p);
|
||||
};
|
||||
|
|
|
@ -35,6 +35,7 @@ static const double selectionLassoWidth = 2.0; // Border between title and char
|
|||
|
||||
StatsView::StatsView(QQuickItem *parent) : QQuickItem(parent),
|
||||
backgroundDirty(true),
|
||||
currentTheme(statsThemes[0]),
|
||||
highlightedSeries(nullptr),
|
||||
xAxis(nullptr),
|
||||
yAxis(nullptr),
|
||||
|
@ -99,7 +100,7 @@ void StatsView::mousePressEvent(QMouseEvent *event)
|
|||
if (selectionRect)
|
||||
deleteChartItem(selectionRect); // Ooops. Already a selection in place.
|
||||
dragStartMouse = pos;
|
||||
selectionRect = createChartItem<ChartRectLineItem>(ChartZValue::Selection, selectionLassoColor, selectionLassoWidth);
|
||||
selectionRect = createChartItem<ChartRectLineItem>(ChartZValue::Selection, currentTheme->selectionLassoColor, selectionLassoWidth);
|
||||
selectionModifier = modifier;
|
||||
oldSelection = modifier.ctrl ? getDiveSelection() : std::vector<dive *>();
|
||||
grabMouse();
|
||||
|
@ -143,7 +144,7 @@ RootNode::RootNode(StatsView &view) : view(view)
|
|||
// also be done on the widget level, but would have to be done
|
||||
// separately for desktop and mobile, so do it here.
|
||||
backgroundNode.reset(view.w()->createRectangleNode());
|
||||
backgroundNode->setColor(backgroundColor);
|
||||
backgroundNode->setColor(view.getCurrentTheme().backgroundColor);
|
||||
appendChildNode(backgroundNode.get());
|
||||
|
||||
for (auto &zNode: zNodes) {
|
||||
|
@ -184,7 +185,7 @@ QSGNode *StatsView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNod
|
|||
}
|
||||
|
||||
for (ChartItem *item = dirtyItems.first; item; item = item->next) {
|
||||
item->render();
|
||||
item->render(*currentTheme);
|
||||
item->dirty = false;
|
||||
}
|
||||
dirtyItems.splice(cleanItems);
|
||||
|
@ -299,6 +300,18 @@ QQuickWindow *StatsView::w() const
|
|||
return window();
|
||||
}
|
||||
|
||||
void StatsView::setTheme(int idx)
|
||||
{
|
||||
idx = std::clamp(idx, 0, (int)statsThemes.size() - 1);
|
||||
currentTheme = statsThemes[idx];
|
||||
rootNode->backgroundNode->setColor(currentTheme->backgroundColor);
|
||||
}
|
||||
|
||||
const StatsTheme &StatsView::getCurrentTheme() const
|
||||
{
|
||||
return *currentTheme;
|
||||
}
|
||||
|
||||
QSizeF StatsView::size() const
|
||||
{
|
||||
return boundingRect().size();
|
||||
|
@ -459,7 +472,7 @@ void StatsView::setTitle(const QString &s)
|
|||
return;
|
||||
}
|
||||
title = createChartItem<ChartTextItem>(ChartZValue::Legend, titleFont, s);
|
||||
title->setColor(darkLabelColor);
|
||||
title->setColor(currentTheme->darkLabelColor);
|
||||
}
|
||||
|
||||
void StatsView::updateTitlePos()
|
||||
|
@ -1128,10 +1141,10 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives,
|
|||
if (categoryVariable->type() == StatsVariable::Type::Numeric) {
|
||||
double mean = categoryVariable->mean(dives);
|
||||
if (!std::isnan(mean))
|
||||
meanMarker = createChartItem<HistogramMarker>(mean, isHorizontal, meanMarkerColor, xAxis, yAxis);
|
||||
meanMarker = createChartItem<HistogramMarker>(mean, isHorizontal, currentTheme->meanMarkerColor, xAxis, yAxis);
|
||||
double median = categoryVariable->quartiles(dives).q2;
|
||||
if (!std::isnan(median))
|
||||
medianMarker = createChartItem<HistogramMarker>(median, isHorizontal, medianMarkerColor, xAxis, yAxis);
|
||||
medianMarker = createChartItem<HistogramMarker>(median, isHorizontal, currentTheme->medianMarkerColor, xAxis, yAxis);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ class QuartileMarker;
|
|||
class RegressionItem;
|
||||
class StatsAxis;
|
||||
class StatsGrid;
|
||||
class StatsTheme;
|
||||
class Legend;
|
||||
class QSGTexture;
|
||||
class RootNode; // Internal implementation detail
|
||||
|
@ -53,6 +54,8 @@ public:
|
|||
QQuickWindow *w() const; // Make window available to items
|
||||
QSizeF size() const;
|
||||
QRectF plotArea() const;
|
||||
void setTheme(int idx); // Invalid indexes will result in the default theme. Chart must be replot for theme to become effective.
|
||||
const StatsTheme &getCurrentTheme() const;
|
||||
void addQSGNode(QSGNode *node, ChartZValue z); // Must only be called in render thread!
|
||||
void registerChartItem(ChartItem &item);
|
||||
void registerDirtyChartItem(ChartItem &item);
|
||||
|
@ -140,6 +143,7 @@ private:
|
|||
void addLineMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal);
|
||||
|
||||
StatsState state;
|
||||
const StatsTheme *currentTheme;
|
||||
QFont titleFont;
|
||||
std::vector<std::unique_ptr<StatsSeries>> series;
|
||||
std::unique_ptr<StatsGrid> grid;
|
||||
|
|
Loading…
Add table
Reference in a new issue