redesign species distribution

This commit is contained in:
cawtds
2026-07-19 13:08:14 +02:00
parent a430b3e203
commit bce2a61c3c
7 changed files with 216 additions and 48 deletions

View File

@@ -92,7 +92,7 @@
<number>0</number>
</property>
<item>
<widget class="QChartView" name="chartView_SpeciesDistribution">
<widget class="SpeciesChartView" name="chartView_SpeciesDistribution">
<property name="renderHints">
<set>QPainter::Antialiasing</set>
</property>
@@ -216,7 +216,7 @@
</widget>
</item>
<item>
<widget class="QChartView" name="chartView_LevelDistribution">
<widget class="SpeciesChartView" name="chartView_LevelDistribution">
<property name="renderHints">
<set>QPainter::Antialiasing</set>
</property>
@@ -230,9 +230,9 @@
</widget>
<customwidgets>
<customwidget>
<class>QChartView</class>
<extends>QGraphicsView</extends>
<header>QtCharts</header>
<class>SpeciesChartView</class>
<extends>QChartView</extends>
<header>specieschartview.h</header>
</customwidget>
<customwidget>
<class>NoScrollComboBox</class>

View File

@@ -0,0 +1,37 @@
#ifndef SPECIESCHARTVIEW_H
#define SPECIESCHARTVIEW_H
#include <QtCharts/QChartView>
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<SpeciesEntry> m_speciesEntries;
Project *m_project = nullptr;
int m_maxTextWidth = 0;
};
#endif

View File

@@ -8,6 +8,8 @@
#if __has_include(<QtCharts>)
#include <QtCharts>
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;

View File

@@ -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 \

View File

@@ -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);

100
src/ui/specieschartview.cpp Normal file
View File

@@ -0,0 +1,100 @@
#include "specieschartview.h"
#include "project.h"
#include <QPainter>
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<int>(y - rowHeight / 2.0),
m_maxTextWidth,
static_cast<int>(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);
}
}

View File

@@ -19,9 +19,10 @@ static const QList<QPair<QString, QChart::ChartTheme>> 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<double> tableFrequencies = this->table->percentages();
const QVector<WildPokemon> 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<QBarSet*> barSets;
QStringList categories;
QList<SpeciesFrequency> 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<QAbstractBarSeries*>(chart->series().at(0))->barSets());
chart = ui->chartView_LevelDistribution->chart();
if (!chart || chart->series().isEmpty())
return;
saveSpeciesColors(static_cast<QAbstractBarSeries*>(chart->series().at(0))->barSets());
chart->setTheme(theme);
applySpeciesColors(static_cast<QAbstractBarSeries*>(chart->series().at(0))->barSets());
}