diff --git a/forms/wildmonchart.ui b/forms/wildmonchart.ui index fec086dd..cfba0409 100644 --- a/forms/wildmonchart.ui +++ b/forms/wildmonchart.ui @@ -17,10 +17,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -48,7 +48,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -85,6 +85,9 @@ 0 + + 0 + 0 @@ -92,12 +95,56 @@ 0 - - - QPainter::Antialiasing + + + QFrame::Shape::NoFrame + + QFrame::Shadow::Plain + + + + 12 + + + 0 + + + + + Group + + + + + + + QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon + + + 8 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + @@ -120,10 +167,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -142,7 +189,7 @@ - QComboBox::AdjustToMinimumContentsLengthWithIcon + QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon 8 @@ -152,7 +199,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -199,10 +246,10 @@ true - QComboBox::NoInsert + QComboBox::InsertPolicy::NoInsert - QComboBox::AdjustToMinimumContentsLengthWithIcon + QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon 12 @@ -216,7 +263,7 @@ - + QPainter::Antialiasing @@ -229,16 +276,16 @@ - - QChartView - QGraphicsView -
QtCharts
-
NoScrollComboBox QComboBox
noscrollwidgets.h
+ + SpeciesChartView + QWidget +
specieschartview.h
+
diff --git a/include/ui/specieschartview.h b/include/ui/specieschartview.h new file mode 100644 index 00000000..63d3a89b --- /dev/null +++ b/include/ui/specieschartview.h @@ -0,0 +1,45 @@ +#ifndef SPECIESCHARTVIEW_H +#define SPECIESCHARTVIEW_H + +#if QT_CHARTS_LIB +#include + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +QT_CHARTS_USE_NAMESPACE +#endif + +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; + static constexpr int MaxLabelWidth = 230; + 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 // __has_include() + +#endif // SPECIESCHARTVIEW_H diff --git a/include/ui/wildmonchart.h b/include/ui/wildmonchart.h index 0ebc26f0..86f50507 100644 --- a/include/ui/wildmonchart.h +++ b/include/ui/wildmonchart.h @@ -5,6 +5,8 @@ #include +class Project; + #if __has_include() #include @@ -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; @@ -46,8 +49,6 @@ private: typedef QMap GroupedData; QMap speciesToGroupedData; - QMap speciesToColor; - QStringList getSpeciesNamesAlphabetical() const; double getSpeciesFrequency(const QString&, const QString&) const; @@ -63,8 +64,6 @@ private: void refreshSpeciesDistributionChart(); void refreshLevelDistributionChart(); - void saveSpeciesColors(const QList &); - void applySpeciesColors(const QList &); QChart::ChartTheme currentTheme() const; void updateTheme(); void limitChartAnimation(); @@ -78,7 +77,7 @@ class WildMonChart : public QWidget { Q_OBJECT public: - explicit WildMonChart(QWidget *, const EncounterTableModel *) {}; + explicit WildMonChart(QWidget *, const EncounterTableModel *, Project *) {}; ~WildMonChart() {}; public slots: 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..7da502ef --- /dev/null +++ b/src/ui/specieschartview.cpp @@ -0,0 +1,107 @@ +#ifdef QT_CHARTS_LIB + +#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; + if (m_project) icon = m_project->getSpeciesIcon(speciesPrefix + name); + + m_speciesEntries.push_back({ + name, + icon.scaled( + IconSize, + IconSize, + Qt::KeepAspectRatio, + Qt::SmoothTransformation) + }); + } + + if (m_maxTextWidth > MaxLabelWidth) { + m_maxTextWidth = MaxLabelWidth; + } + + 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); + } +} + +#endif // QT_CHARTS_LIB diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 5419f6f4..d78c8ce2 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); @@ -34,6 +35,7 @@ WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) : connect(ui->groupBox_Species, &QGroupBox::clicked, this, &WildMonChart::refreshLevelDistributionChart); connect(ui->comboBox_Species, &QComboBox::currentTextChanged, this, &WildMonChart::refreshLevelDistributionChart); connect(ui->comboBox_Group, &QComboBox::currentTextChanged, this, &WildMonChart::refreshLevelDistributionChart); + connect(ui->comboBox_speciesDistributionGroup, &QComboBox::currentTextChanged, this, &WildMonChart::refreshSpeciesDistributionChart); connect(ui->tabWidget, &QTabWidget::currentChanged, this, &WildMonChart::limitChartAnimation); @@ -76,15 +78,18 @@ void WildMonChart::clearTableData() { this->tableIndexToGroupName.clear(); this->groupedLevelRanges.clear(); this->speciesToGroupedData.clear(); - this->speciesToColor.clear(); setWindowTitle(baseWindowTitle); const QSignalBlocker blocker1(ui->comboBox_Species); const QSignalBlocker blocker2(ui->comboBox_Group); + const QSignalBlocker blocker3(ui->comboBox_speciesDistributionGroup); ui->comboBox_Species->clear(); ui->comboBox_Group->clear(); ui->comboBox_Group->setEnabled(false); + ui->comboBox_speciesDistributionGroup->clear(); + ui->comboBox_speciesDistributionGroup->setEnabled(false); ui->label_Group->setEnabled(false); + ui->label_speciesDistributionGroup->setEnabled(false); } // Extract all the data from the table that we need for the charts @@ -109,7 +114,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; @@ -149,20 +154,27 @@ void WildMonChart::readTable() { // Populate combo boxes const QSignalBlocker blocker1(ui->comboBox_Species); const QSignalBlocker blocker2(ui->comboBox_Group); + const QSignalBlocker blocker3(ui->comboBox_speciesDistributionGroup); ui->comboBox_Species->clear(); ui->comboBox_Species->addItems(getSpeciesNamesAlphabetical()); ui->comboBox_Group->clear(); ui->comboBox_Group->addItems(this->groupNames); + ui->comboBox_speciesDistributionGroup->clear(); + ui->comboBox_speciesDistributionGroup->addItems(this->groupNames); bool enableGroupSelection = usesGroupLabels(); ui->comboBox_Group->setEnabled(enableGroupSelection); + ui->comboBox_speciesDistributionGroup->setEnabled(enableGroupSelection); ui->label_Group->setEnabled(enableGroupSelection); + ui->label_speciesDistributionGroup->setEnabled(enableGroupSelection); } void WildMonChart::refresh() { const QSignalBlocker blocker1(ui->comboBox_Species); const QSignalBlocker blocker2(ui->comboBox_Group); + const QSignalBlocker blocker3(ui->comboBox_speciesDistributionGroup); const QString oldSpecies = ui->comboBox_Species->currentText(); const QString oldGroup = ui->comboBox_Group->currentText(); + const QString oldSpeciesGroup = ui->comboBox_speciesDistributionGroup->currentText(); readTable(); @@ -173,6 +185,9 @@ void WildMonChart::refresh() { index = ui->comboBox_Group->findText(oldGroup); if (index >= 0) ui->comboBox_Group->setCurrentIndex(index); + index = ui->comboBox_speciesDistributionGroup->findText(oldSpeciesGroup); + if (index >= 0) ui->comboBox_speciesDistributionGroup->setCurrentIndex(index); + refreshSpeciesDistributionChart(); refreshLevelDistributionChart(); } @@ -180,7 +195,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 +242,91 @@ bool WildMonChart::usesGroupLabels() const { return this->groupNames.length() > 1; } +struct SpeciesFrequency +{ + QString name; + double frequency; +}; + QChart* WildMonChart::createSpeciesDistributionChart() { + const QString groupName = ui->comboBox_speciesDistributionGroup->currentText(); 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; - } - } - - // 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); - }); + speciesList.append({species, getSpeciesFrequency(species, groupName) * 100.0}); } - // 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) { + if (entry.frequency > 0.0) { + 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(); - // X-axis is the % frequency. We're already showing percentages on the bar, so we just display 0/50/100% + QFontMetrics fm(chart->font()); + + int maxTextWidth = 0; + for (const QString &category : categories) { + maxTextWidth = std::max(maxTextWidth, fm.horizontalAdvance(category)); + + if (maxTextWidth > SpeciesChartView::MaxLabelWidth) { + maxTextWidth = SpeciesChartView::MaxLabelWidth; + break; + } + } + + 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. 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; } @@ -333,7 +379,7 @@ QChart* WildMonChart::createLevelDistributionChart() { levelRange = getLevelRange(species, groupName); } else { // Species box is inactive, we display data for all species in the table. - for (const auto &species : this->speciesInLegendOrder) + for (const auto &species : getSpeciesNamesAlphabetical()) barSets.append(createLevelDistributionBarSet(species, groupName, false)); levelRange = this->groupedLevelRanges.value(groupName); } @@ -351,7 +397,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,24 +434,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; + chart->setTheme(theme); - applySpeciesColors(static_cast(chart->series().at(0))->barSets()); -} - -void WildMonChart::saveSpeciesColors(const QList &barSets) { - this->speciesToColor.clear(); - for (auto set : barSets) - this->speciesToColor.insert(set->label(), set->color()); -} - -void WildMonChart::applySpeciesColors(const QList &barSets) { - for (auto set : barSets) - set->setColor(this->speciesToColor.value(set->label())); } // Turn off the chart animation once it's played, otherwise it replays any time the window changes size.