[DeckAnalytics] Enforce WUBRGC ordering for analytics. (#6509)

* [DeckAnalytics] Enforce WUBRGC ordering for analytics.

Took 6 minutes

Took 7 seconds

* Include QSet

Took 51 seconds

* Move include out of namespace.

Took 6 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-01-14 11:25:45 +01:00 committed by GitHub
parent 21d60ec3f1
commit 289b139be9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 87 additions and 57 deletions

View file

@ -8,6 +8,7 @@
#include <QDialog> #include <QDialog>
#include <QListWidget> #include <QListWidget>
#include <libcockatrice/utility/color.h>
namespace namespace
{ {
@ -71,14 +72,16 @@ void ManaBaseWidget::updateDisplay()
// Choose display mode // Choose display mode
if (config.displayType == "bar") { if (config.displayType == "bar") {
QHash<QString, QColor> colors = {{"W", QColor(248, 231, 185)}, {"U", QColor(14, 104, 171)}, const QList<QPair<QString, int>> sortedColors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(mapSorted);
{"B", QColor(21, 11, 0)}, {"R", QColor(211, 32, 42)}, static const QHash<QString, QColor> colorMap = {
{"G", QColor(0, 115, 62)}, {"C", QColor(150, 150, 150)}}; {"W", QColor(248, 231, 185)}, {"U", QColor(14, 104, 171)}, {"B", QColor(21, 11, 0)},
{"R", QColor(211, 32, 42)}, {"G", QColor(0, 115, 62)}, {"C", QColor(150, 150, 150)},
};
for (auto color : manaMap.keys()) { for (const auto &[color, count] : sortedColors) {
QString label = QString("%1 %2 (%3)").arg(color).arg(manaMap[color]).arg(cardCount.value(color)); QString label = QString("%1 %2 (%3)").arg(color).arg(count).arg(cardCount.value(color));
BarWidget *bar = new BarWidget(label, manaMap[color], highest, colors.value(color, Qt::gray), this); BarWidget *bar = new BarWidget(label, count, highest, colorMap.value(color, Qt::gray), this);
barLayout->addWidget(bar); barLayout->addWidget(bar);
} }

View file

@ -1,18 +1,21 @@
#include "color_bar.h" #include "color_bar.h"
#include "libcockatrice/utility/color.h"
#include <QLinearGradient> #include <QLinearGradient>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter> #include <QPainter>
#include <QToolTip> #include <QToolTip>
ColorBar::ColorBar(const QMap<QString, int> &_colors, QWidget *parent) : QWidget(parent), colors(_colors) ColorBar::ColorBar(const QMap<QString, int> &_colors, QWidget *parent)
: QWidget(parent), colors(GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors))
{ {
setMouseTracking(true); setMouseTracking(true);
} }
void ColorBar::setColors(const QMap<QString, int> &_colors) void ColorBar::setColors(const QMap<QString, int> &_colors)
{ {
colors = _colors; colors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors);
update(); update();
} }
@ -27,8 +30,8 @@ void ColorBar::paintEvent(QPaintEvent *)
return; return;
int total = 0; int total = 0;
for (int v : colors.values()) for (const auto &pair : colors)
total += v; total += pair.second;
// Prevent divide-by-zero // Prevent divide-by-zero
if (total == 0) if (total == 0)
@ -50,15 +53,9 @@ void ColorBar::paintEvent(QPaintEvent *)
// Clip to inside the border // Clip to inside the border
p.setClipRect(bounds.adjusted(2, 2, -2, -2)); p.setClipRect(bounds.adjusted(2, 2, -2, -2));
// Ensure predictable order // Draw segments IN ORDER
QList<QString> sortedKeys = colors.keys(); for (const auto &[key, value] : colors) {
std::sort(sortedKeys.begin(), sortedKeys.end()); // Sort alphabetically const double ratio = double(value) / total;
// Draw each color segment in the sorted order
for (const QString &key : sortedKeys) {
int value = colors[key];
double ratio = double(value) / total;
if (ratio <= minRatioThreshold) { if (ratio <= minRatioThreshold) {
continue; continue;
} }
@ -122,20 +119,21 @@ void ColorBar::mouseMoveEvent(QMouseEvent *event)
QString ColorBar::tooltipForPosition(int x) const QString ColorBar::tooltipForPosition(int x) const
{ {
int total = 0; int total = 0;
for (int v : colors.values()) for (const auto &pair : colors)
total += v; total += pair.second;
if (total == 0) if (total == 0)
return {}; return {};
int pos = 0; int pos = 0;
for (auto it = colors.cbegin(); it != colors.cend(); ++it) {
const double ratio = double(it.value()) / total; for (const auto &[key, value] : colors) {
const double ratio = double(value) / total;
const int segmentWidth = int(ratio * width()); const int segmentWidth = int(ratio * width());
if (x >= pos && x < pos + segmentWidth) { if (x >= pos && x < pos + segmentWidth) {
const double percent = (100.0 * it.value()) / total; const double percent = (100.0 * value) / total;
return QString("%1: %2 cards (%3%)").arg(it.key()).arg(it.value()).arg(QString::number(percent, 'f', 1)); return QString("%1: %2 cards (%3%)").arg(key).arg(value).arg(QString::number(percent, 'f', 1));
} }
pos += segmentWidth; pos += segmentWidth;

View file

@ -100,7 +100,7 @@ protected:
private: private:
/// Map of color keys to counts used for rendering. /// Map of color keys to counts used for rendering.
QMap<QString, int> colors; QList<QPair<QString, int>> colors;
/// True if the mouse is currently inside the widget. /// True if the mouse is currently inside the widget.
bool isHovered = false; bool isHovered = false;

View file

@ -1,18 +1,21 @@
#include "color_pie.h" #include "color_pie.h"
#include "libcockatrice/utility/color.h"
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter> #include <QPainter>
#include <QToolTip> #include <QToolTip>
#include <QtMath> #include <QtMath>
ColorPie::ColorPie(const QMap<QString, int> &_colors, QWidget *parent) : QWidget(parent), colors(_colors) ColorPie::ColorPie(const QMap<QString, int> &_colors, QWidget *parent)
: QWidget(parent), colors(GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors))
{ {
setMouseTracking(true); setMouseTracking(true);
} }
void ColorPie::setColors(const QMap<QString, int> &_colors) void ColorPie::setColors(const QMap<QString, int> &_colors)
{ {
colors = _colors; colors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors);
update(); update();
} }
@ -28,8 +31,8 @@ void ColorPie::paintEvent(QPaintEvent *)
} }
int total = 0; int total = 0;
for (int v : colors.values()) { for (const auto &pair : colors) {
total += v; total += pair.second;
} }
if (total == 0) { if (total == 0) {
@ -41,24 +44,18 @@ void ColorPie::paintEvent(QPaintEvent *)
int w = width(); int w = width();
int h = height(); int h = height();
int size = qMin(w, h) - 40; // leave space for labels int size = qMin(w, h) - 40;
QRectF rect((w - size) / 2.0, (h - size) / 2.0, size, size); QRectF rect((w - size) / 2.0, (h - size) / 2.0, size, size);
// Draw border // Border
p.setPen(QPen(Qt::black, 1)); p.setPen(QPen(Qt::black, 1));
p.setBrush(Qt::NoBrush); p.setBrush(Qt::NoBrush);
p.drawEllipse(rect); p.drawEllipse(rect);
// Sorted keys for predictable order
QList<QString> sortedKeys = colors.keys();
std::sort(sortedKeys.begin(), sortedKeys.end());
double startAngle = 0.0; double startAngle = 0.0;
for (const QString &key : sortedKeys) { for (const auto &[key, value] : colors) {
int value = colors[key];
double ratio = double(value) / total; double ratio = double(value) / total;
if (ratio <= minRatioThreshold) { if (ratio <= minRatioThreshold) {
continue; continue;
} }
@ -67,20 +64,18 @@ void ColorPie::paintEvent(QPaintEvent *)
QColor base = colorFromName(key); QColor base = colorFromName(key);
// Gradient
QRadialGradient grad(rect.center(), size / 2); QRadialGradient grad(rect.center(), size / 2);
grad.setColorAt(0, base.lighter(130)); grad.setColorAt(0, base.lighter(130));
grad.setColorAt(1, base.darker(130)); grad.setColorAt(1, base.darker(130));
p.setBrush(grad); p.setBrush(grad);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
// Draw slice
p.drawPie(rect, int(startAngle * 16), int(spanAngle * 16)); p.drawPie(rect, int(startAngle * 16), int(spanAngle * 16));
// Draw percent label // Percent label
double midAngle = startAngle + spanAngle / 2; double midAngle = startAngle + spanAngle / 2.0;
double rad = qDegreesToRadians(midAngle); double rad = qDegreesToRadians(midAngle);
double labelRadius = size / 2 + 15; // slightly outside the pie double labelRadius = size / 2 + 15;
QPointF center = rect.center(); QPointF center = rect.center();
QPointF labelPos(center.x() + labelRadius * qCos(rad), center.y() - labelRadius * qSin(rad)); QPointF labelPos(center.x() + labelRadius * qCos(rad), center.y() - labelRadius * qSin(rad));
@ -147,10 +142,13 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const
} }
int total = 0; int total = 0;
for (int v : colors.values()) for (const auto &pair : colors) {
total += v; total += pair.second;
if (total == 0) }
if (total == 0) {
return {}; return {};
}
int w = width(); int w = width();
int h = height(); int h = height();
@ -158,9 +156,9 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const
QPointF center(w / 2.0, h / 2.0); QPointF center(w / 2.0, h / 2.0);
QPointF v = pt - center; QPointF v = pt - center;
double distance = std::sqrt(v.x() * v.x() + v.y() * v.y()); double distance = std::hypot(v.x(), v.y());
if (distance > size / 2.0) if (distance > size / 2.0)
return {}; // outside pie return {};
double angle = std::atan2(-v.y(), v.x()) * 180.0 / M_PI; double angle = std::atan2(-v.y(), v.x()) * 180.0 / M_PI;
if (angle < 0) { if (angle < 0) {
@ -169,16 +167,19 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const
double acc = 0.0; double acc = 0.0;
QList<QString> keys = colors.keys(); for (const auto &[key, value] : colors) {
std::sort(keys.begin(), keys.end()); double ratio = double(value) / total;
if (ratio <= minRatioThreshold) {
continue;
}
for (const QString &key : keys) { double span = ratio * 360.0;
double span = (double(colors[key]) / total) * 360.0;
if (angle >= acc && angle < acc + span) { if (angle >= acc && angle < acc + span) {
double percent = (100.0 * colors[key]) / total; double percent = (100.0 * value) / total;
return QString("%1: %2 cards (%3%)").arg(key).arg(colors[key]).arg(QString::number(percent, 'f', 1)); return QString("%1: %2 cards (%3%)").arg(key).arg(value).arg(QString::number(percent, 'f', 1));
} }
acc += span; acc += span;
} }

View file

@ -31,7 +31,7 @@ protected:
void mouseMoveEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override;
private: private:
QMap<QString, int> colors; QList<QPair<QString, int>> colors;
bool isHovered = false; bool isHovered = false;
const double minRatioThreshold = 0.01; // skip tiny slices const double minRatioThreshold = 0.01; // skip tiny slices

View file

@ -22,6 +22,8 @@ inline color convertQColorToColor(const QColor &c)
return result; return result;
} }
#include <QSet>
namespace GameSpecificColors namespace GameSpecificColors
{ {
namespace MTG namespace MTG
@ -56,6 +58,32 @@ inline QColor colorHelper(const QString &name)
return QColor(r, g, b); return QColor(r, g, b);
} }
inline QList<QPair<QString, int>> sortManaMapWUBRGCFirst(const QMap<QString, int> &input)
{
static const QStringList priorityOrder = {"W", "U", "B", "R", "G", "C"};
QList<QPair<QString, int>> result;
QSet<QString> consumed;
// 1. Add priority colors in fixed order
for (const QString &key : priorityOrder) {
auto it = input.find(key);
if (it != input.end()) {
result.append({it.key(), it.value()});
consumed.insert(it.key());
}
}
// 2. Add remaining keys (QMap iteration is already sorted)
for (auto it = input.begin(); it != input.end(); ++it) {
if (!consumed.contains(it.key())) {
result.append({it.key(), it.value()});
}
}
return result;
}
} // namespace MTG } // namespace MTG
} // namespace GameSpecificColors } // namespace GameSpecificColors
#endif #endif