From 289b139be98d4c1799e46e647ddd4aaa08f2ae21 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:25:45 +0100 Subject: [PATCH] [DeckAnalytics] Enforce WUBRGC ordering for analytics. (#6509) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [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 --- .../mana_base/mana_base_widget.cpp | 15 +++-- .../general/display/charts/bars/color_bar.cpp | 36 ++++++----- .../general/display/charts/bars/color_bar.h | 2 +- .../general/display/charts/pies/color_pie.cpp | 61 ++++++++++--------- .../general/display/charts/pies/color_pie.h | 2 +- .../libcockatrice/utility/color.h | 28 +++++++++ 6 files changed, 87 insertions(+), 57 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp index d38314b8c..0093c360b 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace { @@ -71,14 +72,16 @@ void ManaBaseWidget::updateDisplay() // Choose display mode if (config.displayType == "bar") { - QHash 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> sortedColors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(mapSorted); + static const QHash 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); } diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp index 94e2420b5..ed91ba03d 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp @@ -1,18 +1,21 @@ #include "color_bar.h" +#include "libcockatrice/utility/color.h" + #include #include #include #include -ColorBar::ColorBar(const QMap &_colors, QWidget *parent) : QWidget(parent), colors(_colors) +ColorBar::ColorBar(const QMap &_colors, QWidget *parent) + : QWidget(parent), colors(GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors)) { setMouseTracking(true); } void ColorBar::setColors(const QMap &_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 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; diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h index f61ab6942..0ef68f578 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h @@ -100,7 +100,7 @@ protected: private: /// Map of color keys to counts used for rendering. - QMap colors; + QList> colors; /// True if the mouse is currently inside the widget. bool isHovered = false; diff --git a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp index e86793083..84232b36f 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp @@ -1,18 +1,21 @@ #include "color_pie.h" +#include "libcockatrice/utility/color.h" + #include #include #include #include -ColorPie::ColorPie(const QMap &_colors, QWidget *parent) : QWidget(parent), colors(_colors) +ColorPie::ColorPie(const QMap &_colors, QWidget *parent) + : QWidget(parent), colors(GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors)) { setMouseTracking(true); } void ColorPie::setColors(const QMap &_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 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 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; } diff --git a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h index a8fe784a3..7d71ea3b9 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h +++ b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h @@ -31,7 +31,7 @@ protected: void mouseMoveEvent(QMouseEvent *event) override; private: - QMap colors; + QList> colors; bool isHovered = false; const double minRatioThreshold = 0.01; // skip tiny slices diff --git a/libcockatrice_utility/libcockatrice/utility/color.h b/libcockatrice_utility/libcockatrice/utility/color.h index bf4759565..f02df3a0e 100644 --- a/libcockatrice_utility/libcockatrice/utility/color.h +++ b/libcockatrice_utility/libcockatrice/utility/color.h @@ -22,6 +22,8 @@ inline color convertQColorToColor(const QColor &c) return result; } +#include + namespace GameSpecificColors { namespace MTG @@ -56,6 +58,32 @@ inline QColor colorHelper(const QString &name) return QColor(r, g, b); } + +inline QList> sortManaMapWUBRGCFirst(const QMap &input) +{ + static const QStringList priorityOrder = {"W", "U", "B", "R", "G", "C"}; + + QList> result; + QSet 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