From bce2a61c3cdb25adf09344cf08a8b68473f68c41 Mon Sep 17 00:00:00 2001 From: cawtds <38510667+cawtds@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:08:14 +0200 Subject: [PATCH] redesign species distribution --- forms/wildmonchart.ui | 10 ++-- include/ui/specieschartview.h | 37 ++++++++++++ include/ui/wildmonchart.h | 5 +- porymap.pro | 2 + src/mainwindow.cpp | 2 +- src/ui/specieschartview.cpp | 100 +++++++++++++++++++++++++++++++ src/ui/wildmonchart.cpp | 108 +++++++++++++++++++++------------- 7 files changed, 216 insertions(+), 48 deletions(-) create mode 100644 include/ui/specieschartview.h create mode 100644 src/ui/specieschartview.cpp diff --git a/forms/wildmonchart.ui b/forms/wildmonchart.ui index fec086dd..c4a4a8e5 100644 --- a/forms/wildmonchart.ui +++ b/forms/wildmonchart.ui @@ -92,7 +92,7 @@ 0 - + QPainter::Antialiasing @@ -216,7 +216,7 @@ - + QPainter::Antialiasing @@ -230,9 +230,9 @@ - QChartView - QGraphicsView -
QtCharts
+ SpeciesChartView + QChartView +
specieschartview.h
NoScrollComboBox diff --git a/include/ui/specieschartview.h b/include/ui/specieschartview.h new file mode 100644 index 00000000..cfb66be8 --- /dev/null +++ b/include/ui/specieschartview.h @@ -0,0 +1,37 @@ +#ifndef SPECIESCHARTVIEW_H +#define SPECIESCHARTVIEW_H + +#include + +class Project; + +struct SpeciesEntry +{ + QString name; + QPixmap icon; +}; + +class SpeciesChartView : public QChartView +{ + Q_OBJECT + +public: + static constexpr int IconSize = 32; + static constexpr int IconVerticalOffset = -2; + static constexpr int Spacing = 6; + static constexpr int Padding = 10; + explicit SpeciesChartView(QWidget *parent = nullptr); + + void setSpecies(const QStringList &species); + void setProject(Project *project); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QVector m_speciesEntries; + Project *m_project = nullptr; + int m_maxTextWidth = 0; +}; + +#endif diff --git a/include/ui/wildmonchart.h b/include/ui/wildmonchart.h index 0ebc26f0..0b0d6f9a 100644 --- a/include/ui/wildmonchart.h +++ b/include/ui/wildmonchart.h @@ -8,6 +8,8 @@ #if __has_include() #include +class Project; + namespace Ui { class WildMonChart; } @@ -16,7 +18,7 @@ class WildMonChart : public QWidget { Q_OBJECT public: - explicit WildMonChart(QWidget *parent, const EncounterTableModel *table); + explicit WildMonChart(QWidget *parent, const EncounterTableModel *table, Project *project); ~WildMonChart(); public slots: @@ -27,6 +29,7 @@ public slots: private: Ui::WildMonChart *ui; const EncounterTableModel *table; + Project *project = nullptr; QStringList groupNames; QStringList groupNamesReversed; diff --git a/porymap.pro b/porymap.pro index d2463f5d..199dc26b 100644 --- a/porymap.pro +++ b/porymap.pro @@ -144,6 +144,7 @@ SOURCES += src/config/keyvalueconfigbase.cpp \ src/ui/palettecolorsearch.cpp \ src/ui/paletteeditor.cpp \ src/ui/selectablepixmapitem.cpp \ + src/ui/specieschartview.cpp \ src/ui/tileseteditor.cpp \ src/ui/tileseteditormetatileselector.cpp \ src/ui/tileseteditortileselector.cpp \ @@ -275,6 +276,7 @@ HEADERS += include/config/keyvalueconfigbase.h \ include/ui/palettecolorsearch.h \ include/ui/paletteeditor.h \ include/ui/selectablepixmapitem.h \ + include/ui/specieschartview.h \ include/ui/tileseteditor.h \ include/ui/tileseteditormetatileselector.h \ include/ui/tileseteditortileselector.h \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6d19344a..5dd547c4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2839,7 +2839,7 @@ void MainWindow::on_pushButton_DeleteWildMonGroup_clicked() { void MainWindow::on_pushButton_SummaryChart_clicked() { if (!this->wildMonChart) { - this->wildMonChart = new WildMonChart(this, this->editor->getCurrentWildMonTable()); + this->wildMonChart = new WildMonChart(this, this->editor->getCurrentWildMonTable(), this->editor->project); connect(this->editor, &Editor::wildMonTableOpened, this->wildMonChart, &WildMonChart::setTable); connect(this->editor, &Editor::wildMonTableClosed, this->wildMonChart, &WildMonChart::clearTable); connect(this->editor, &Editor::wildMonTableEdited, this->wildMonChart, &WildMonChart::refresh); diff --git a/src/ui/specieschartview.cpp b/src/ui/specieschartview.cpp new file mode 100644 index 00000000..68a35eb6 --- /dev/null +++ b/src/ui/specieschartview.cpp @@ -0,0 +1,100 @@ +#include "specieschartview.h" + +#include "project.h" + +#include + +SpeciesChartView::SpeciesChartView(QWidget *parent) + : QChartView(parent) +{ +} + + +void SpeciesChartView::setSpecies(const QStringList &species) +{ + m_speciesEntries.clear(); + + const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); + + QFontMetrics fm(font()); + + m_maxTextWidth = 0; + for (const QString &name : species) + { + m_maxTextWidth = std::max(m_maxTextWidth, fm.horizontalAdvance(name)); + + QPixmap icon = m_project->getSpeciesIcon(speciesPrefix + name); + + m_speciesEntries.push_back({ + name, + icon.scaled( + IconSize, + IconSize, + Qt::KeepAspectRatio, + Qt::SmoothTransformation) + }); + } + + viewport()->update(); +} + +void SpeciesChartView::setProject(Project *project) +{ + m_project = project; + viewport()->update(); +} + +void SpeciesChartView::paintEvent(QPaintEvent *event) +{ + QChartView::paintEvent(event); + + if (!chart() || !m_project || m_speciesEntries.isEmpty()) + return; + + QPainter painter(viewport()); + painter.setRenderHint(QPainter::TextAntialiasing); + painter.setPen(chart()->titleBrush().color()); + + const QRectF plot = chart()->plotArea(); + + if (plot.height() <= 0) + return; + + const QFontMetrics fm(painter.font()); + + const double rowHeight = plot.height() / m_speciesEntries.size(); + const int labelWidth = m_maxTextWidth + IconSize + Spacing; + const int textX = plot.left() - labelWidth - Padding; + const int iconX = textX + m_maxTextWidth + Spacing; + + for (int i = 0; i < m_speciesEntries.size(); ++i) + { + const SpeciesEntry &entry = m_speciesEntries[i]; + + // Center text vertically + const double y = plot.top() + rowHeight * (i + 0.5); + const QRect textRect( + textX, + static_cast(y - rowHeight / 2.0), + m_maxTextWidth, + static_cast(rowHeight) + ); + + painter.drawText( + textRect, + Qt::AlignRight | Qt::AlignVCenter, + entry.name + ); + + // Align icon with text + const int iconY = textRect.center().y() - IconSize / 2.0 + IconVerticalOffset; + const QRect iconRect( + iconX, + iconY, + IconSize, + IconSize + ); + + painter.drawPixmap(iconRect, entry.icon); + } +} diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 5419f6f4..ecdff29f 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -19,9 +19,10 @@ static const QList> themes = { {"Qt", QChart::ChartThemeQt}, }; -WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) : +WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table, Project *project) : QWidget(parent), - ui(new Ui::WildMonChart) + ui(new Ui::WildMonChart), + project(project) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); @@ -109,7 +110,7 @@ void WildMonChart::readTable() { this->groupNames.append(QString()); this->groupNamesReversed.append(QString()); } - + // Read data from the table, combining data for duplicate entries const QVector tableFrequencies = this->table->percentages(); const QVector tablePokemon = this->table->encounterData().wildPokemon; @@ -180,7 +181,11 @@ void WildMonChart::refresh() { void WildMonChart::refreshSpeciesDistributionChart() { if (ui->chartView_SpeciesDistribution->chart()) ui->chartView_SpeciesDistribution->chart()->deleteLater(); - ui->chartView_SpeciesDistribution->setChart(createSpeciesDistributionChart()); + + QChart *chart = createSpeciesDistributionChart(); + ui->chartView_SpeciesDistribution->setChart(chart); + ui->chartView_SpeciesDistribution->setProject(this->project); + ui->chartView_SpeciesDistribution->setSpecies(this->speciesInLegendOrder); if (ui->tabWidget->currentWidget() == ui->tabSpecies) limitChartAnimation(); } @@ -223,64 +228,85 @@ bool WildMonChart::usesGroupLabels() const { return this->groupNames.length() > 1; } +struct SpeciesFrequency +{ + QString name; + double frequency; +}; + QChart* WildMonChart::createSpeciesDistributionChart() { QList barSets; + QStringList categories; + QList speciesList; + for (const auto &species : getSpeciesNamesAlphabetical()) { - // Add encounter chance data - auto set = new QBarSet(species); - for (auto groupName : this->groupNamesReversed) - set->append(getSpeciesFrequency(species, groupName) * 100); - - // We order the bar sets from lowest to highest total, left-to-right. - for (int i = 0; i < barSets.length() + 1; i++){ - if (i >= barSets.length() || barSets.at(i)->sum() > set->sum()) { - barSets.insert(i, set); - break; - } + for (auto groupName : this->groupNamesReversed) { + speciesList.append({species, getSpeciesFrequency(species, groupName) * 100.0}); } - - // Show species name and % when hovering over a bar set. This covers some shortfalls in our ability to control the chart design - // (i.e. bar segments may be too narrow to see the % label, or colors may be hard to match to the legend). - connect(set, &QBarSet::hovered, [set] (bool on, int i) { - QString text = on ? QString("%1 (%2%)").arg(set->label()).arg(set->at(i)) : ""; - QToolTip::showText(QCursor::pos(), text); - }); } - // Preserve the order we set earlier so that the legend isn't shuffling around for the other all-species charts. - this->speciesInLegendOrder.clear(); - for (auto set : barSets) - this->speciesInLegendOrder.append(set->label()); + std::sort(speciesList.begin(), speciesList.end(), [](const auto &a, const auto &b) { + return a.frequency < b.frequency; + }); + + auto *set = new QBarSet("Encounter Rates"); + for (const auto &entry : speciesList) { + set->append(entry.frequency); + categories.append(entry.name); + } + + barSets.append(set); + + // Show species name and % when hovering over a bar set. This covers some shortfalls in our ability to control the chart design + // (i.e. bar segments may be too narrow to see the % label). + connect(set, &QBarSet::hovered, [set] (bool on, int i) { + QString text = on ? QString("%1 (%2%)").arg(set->label()).arg(set->at(i)) : ""; + QToolTip::showText(QCursor::pos(), text); + }); + + // Reverse the order for specieschartview + this->speciesInLegendOrder = categories; + std::reverse(speciesInLegendOrder.begin(), + speciesInLegendOrder.end()); // Set up series - auto series = new QHorizontalPercentBarSeries(); + auto series = new QHorizontalBarSeries(); series->setLabelsVisible(); series->append(barSets); + series->setBarWidth(0.8); // Set up chart auto chart = new QChart(); chart->addSeries(series); chart->setTheme(currentTheme()); chart->setAnimationOptions(QChart::SeriesAnimations); - chart->legend()->setVisible(true); - chart->legend()->setShowToolTips(true); - chart->legend()->setAlignment(Qt::AlignBottom); - saveSpeciesColors(barSets); + chart->legend()->hide(); + + QFontMetrics fm(chart->font()); + + int maxTextWidth = 0; + for (const QString &category : categories) { + maxTextWidth = std::max(maxTextWidth, fm.horizontalAdvance(category)); + } + + int labelWidth = maxTextWidth + SpeciesChartView::IconSize + SpeciesChartView::Spacing; + int leftMargin = labelWidth + 2 * SpeciesChartView::Padding; + chart->setMargins(QMargins(leftMargin, 20, 20, 20)); // X-axis is the % frequency. We're already showing percentages on the bar, so we just display 0/50/100% auto axisX = new QValueAxis(); + axisX->setRange(0, 100); axisX->setLabelFormat("%u%%"); - axisX->setTickCount(3); + axisX->setTickCount(6); chart->addAxis(axisX, Qt::AlignBottom); series->attachAxis(axisX); - // Y-axis is the names of encounter groups (e.g. Old Rod, Good Rod...) - if (usesGroupLabels()) { - auto axisY = new QBarCategoryAxis(); - axisY->setCategories(this->groupNamesReversed); - chart->addAxis(axisY, Qt::AlignLeft); - series->attachAxis(axisY); - } + // Y-axis is species names + icons + auto axisY = new QBarCategoryAxis(); + axisY->setCategories(categories); + axisY->setLabelsVisible(false); // handled in specieschartview.c/h + chart->addAxis(axisY, Qt::AlignLeft); + series->attachAxis(axisY); return chart; } @@ -351,7 +377,6 @@ QChart* WildMonChart::createLevelDistributionChart() { chart->legend()->setVisible(true); chart->legend()->setShowToolTips(true); chart->legend()->setAlignment(Qt::AlignBottom); - applySpeciesColors(barSets); // Has to happen after theme is set // X-axis is the level range. QBarCategoryAxis *axisX = new QBarCategoryAxis(); @@ -389,11 +414,12 @@ void WildMonChart::updateTheme() { if (!chart || chart->series().isEmpty()) return; chart->setTheme(theme); - saveSpeciesColors(static_cast(chart->series().at(0))->barSets()); chart = ui->chartView_LevelDistribution->chart(); if (!chart || chart->series().isEmpty()) return; + + saveSpeciesColors(static_cast(chart->series().at(0))->barSets()); chart->setTheme(theme); applySpeciesColors(static_cast(chart->series().at(0))->barSets()); }