[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
6 changed files with 87 additions and 57 deletions

View File

@@ -8,6 +8,7 @@
#include <QDialog>
#include <QListWidget>
#include <libcockatrice/utility/color.h>
namespace
{
@@ -71,14 +72,16 @@ void ManaBaseWidget::updateDisplay()
// Choose display mode
if (config.displayType == "bar") {
QHash<QString, QColor> colors = {{"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)}};
const QList<QPair<QString, int>> sortedColors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(mapSorted);
static const QHash<QString, QColor> colorMap = {
{"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()) {
QString label = QString("%1 %2 (%3)").arg(color).arg(manaMap[color]).arg(cardCount.value(color));
for (const auto &[color, count] : sortedColors) {
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);
}

View File

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

View File

@@ -100,7 +100,7 @@ protected:
private:
/// 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.
bool isHovered = false;

View File

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

View File

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

View File

@@ -22,6 +22,8 @@ inline color convertQColorToColor(const QColor &c)
return result;
}
#include <QSet>
namespace GameSpecificColors
{
namespace MTG
@@ -56,6 +58,32 @@ inline QColor colorHelper(const QString &name)
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 GameSpecificColors
#endif