Summary chart to horizontal percent bar chart

This commit is contained in:
GriffinR 2024-08-21 16:52:43 -04:00
parent b1814e0e3f
commit 2c65c22b30
5 changed files with 107 additions and 59 deletions

View file

@ -22,9 +22,9 @@ struct WildPokemonHeader {
}; };
struct EncounterField { struct EncounterField {
QString name; QString name; // Ex: "fishing_mons"
QVector<int> encounterRates; QVector<int> encounterRates;
tsl::ordered_map<QString, QVector<int>> groups; tsl::ordered_map<QString, QVector<int>> groups; // Ex: "good_rod", {2, 3, 4}
}; };
typedef QVector<EncounterField> EncounterFields; typedef QVector<EncounterField> EncounterFields;

View file

@ -28,8 +28,9 @@ public:
Slot, Group, Species, MinLevel, MaxLevel, EncounterChance, SlotRatio, EncounterRate, Count Slot, Group, Species, MinLevel, MaxLevel, EncounterChance, SlotRatio, EncounterRate, Count
}; };
WildMonInfo encounterData(); WildMonInfo encounterData() const { return this->monInfo; }
QList<double> percentages() const { return slotPercentages; } EncounterField encounterField() const { return this->encounterFields.at(this->fieldIndex); }
QList<double> percentages() const { return this->slotPercentages; }
void resize(int rows, int cols); void resize(int rows, int cols);
private: private:

View file

@ -13,16 +13,16 @@ class WildMonChart : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit WildMonChart(QWidget *parent, EncounterTableModel *data); explicit WildMonChart(QWidget *parent, EncounterTableModel *table);
~WildMonChart(); ~WildMonChart();
public slots: public slots:
void setChartData(EncounterTableModel *data); void setTable(EncounterTableModel *table);
void updateChart(); void updateChart();
private: private:
Ui::WildMonChart *ui; Ui::WildMonChart *ui;
EncounterTableModel *data; EncounterTableModel *table;
}; };
#endif // WILDMONCHART_H #endif // WILDMONCHART_H

View file

@ -199,7 +199,3 @@ Qt::ItemFlags EncounterTableModel::flags(const QModelIndex &index) const {
} }
return flags | QAbstractTableModel::flags(index); return flags | QAbstractTableModel::flags(index);
} }
WildMonInfo EncounterTableModel::encounterData() {
return this->monInfo;
}

View file

@ -1,9 +1,24 @@
#include "wildmonchart.h" #include "wildmonchart.h"
#include "ui_wildmonchart.h" #include "ui_wildmonchart.h"
#include "log.h"
#include <QtCharts> #include <QtCharts>
WildMonChart::WildMonChart(QWidget *parent, EncounterTableModel *data) : // TODO: Conditional QtCharts -> QtGraphs
// TODO: Handle if num pokemon < values
// TODO: Smarter value precision for %s
// TODO: Move level range onto graph?
// TODO: Draw species icons below legend icons?
// TODO: Match group order in chart visually to group order in table
struct ChartData {
int minLevel;
int maxLevel;
QMap<int, double> values; // One value for each wild encounter group
};
WildMonChart::WildMonChart(QWidget *parent, EncounterTableModel *table) :
QWidget(parent), QWidget(parent),
ui(new Ui::WildMonChart) ui(new Ui::WildMonChart)
{ {
@ -13,82 +28,118 @@ WildMonChart::WildMonChart(QWidget *parent, EncounterTableModel *data) :
ui->chartView->setRenderHint(QPainter::Antialiasing); ui->chartView->setRenderHint(QPainter::Antialiasing);
setChartData(data); setTable(table);
}; };
WildMonChart::~WildMonChart() { WildMonChart::~WildMonChart() {
delete ui; delete ui;
}; };
void WildMonChart::setChartData(EncounterTableModel *data) { void WildMonChart::setTable(EncounterTableModel *table) {
this->data = data; this->table = table;
updateChart(); updateChart();
} }
void WildMonChart::updateChart() { void WildMonChart::updateChart() {
// TODO: Handle empty chart if (!this->table)
if (!this->data)
return; return;
const QList<double> inputValues = data->percentages(); // Read data about encounter groups, e.g. for "fishing_mons" we want to know indexes 2-4 belong to good_rod (group index 1).
const QVector<WildPokemon> inputPokemon = data->encounterData().wildPokemon; // Each group will be represented as a separate bar on the graph.
QList<QString> groupNames;
QList<double> chartValues; QMap<int, int> tableIndexToGroupIndex;
QList<WildPokemon> chartPokemon; int groupIndex = 0;
for (auto groupPair : table->encounterField().groups) {
groupNames.append(groupPair.first);
for (auto i : groupPair.second) {
tableIndexToGroupIndex.insert(i, groupIndex);
}
groupIndex++;
}
const int numGroups = qMax(1, groupNames.length()); // Implicitly 1 group when none are listed
// Combine data for duplicate species entries // Read data from the table, combining data for duplicate species entries
QList<QString> seenSpecies; const QList<double> tableValues = table->percentages();
for (int i = 0; i < qMin(inputValues.length(), inputPokemon.length()); i++) { const QVector<WildPokemon> tablePokemon = table->encounterData().wildPokemon;
const double percent = inputValues.at(i); QMap<QString, ChartData> speciesToChartData;
const WildPokemon pokemon = inputPokemon.at(i); for (int i = 0; i < qMin(tableValues.length(), tablePokemon.length()); i++) {
const double value = tableValues.at(i);
const WildPokemon pokemon = tablePokemon.at(i);
groupIndex = tableIndexToGroupIndex.value(i, 0);
int existingIndex = seenSpecies.indexOf(pokemon.species); if (speciesToChartData.contains(pokemon.species)) {
if (existingIndex >= 0) {
// Duplicate species entry // Duplicate species entry
chartValues[existingIndex] += percent; ChartData *entry = &speciesToChartData[pokemon.species];
if (pokemon.minLevel < chartPokemon.at(existingIndex).minLevel) entry->values[groupIndex] += value;
chartPokemon[existingIndex].minLevel = pokemon.minLevel; if (entry->minLevel > pokemon.minLevel)
if (pokemon.maxLevel > chartPokemon.at(existingIndex).maxLevel) entry->minLevel = pokemon.minLevel;
chartPokemon[existingIndex].maxLevel = pokemon.maxLevel; if (entry->maxLevel < pokemon.maxLevel)
entry->maxLevel = pokemon.maxLevel;
} else { } else {
// New species entry // New species entry
chartValues.append(percent); ChartData entry;
chartPokemon.append(pokemon); entry.minLevel = pokemon.minLevel;
seenSpecies.append(pokemon.species); entry.maxLevel = pokemon.maxLevel;
entry.values.insert(groupIndex, value);
speciesToChartData.insert(pokemon.species, entry);
} }
} }
// TODO: If pokemon < values, fill remainder with an "empty" slice
// Populate chart // Populate chart
//const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::regex_species); // TODO: Change regex to prefix //const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::regex_species); // TODO: Change regex to prefix
QList<QBarSet*> barSets;
const QString speciesPrefix = "SPECIES_"; const QString speciesPrefix = "SPECIES_";
QPieSeries *series = new QPieSeries(); for (auto mapPair = speciesToChartData.cbegin(), end = speciesToChartData.cend(); mapPair != end; mapPair++) {
for (int i = 0; i < qMin(chartValues.length(), chartPokemon.length()); i++) { const ChartData entry = mapPair.value();
const double percent = chartValues.at(i);
const WildPokemon pokemon = chartPokemon.at(i);
// Strip 'SPECIES_' prefix // Strip 'SPECIES_' prefix
QString name = pokemon.species; QString species = mapPair.key();
if (name.startsWith(speciesPrefix)) if (species.startsWith(speciesPrefix))
name.remove(0, speciesPrefix.length()); species.remove(0, speciesPrefix.length());
QString label = QString("%1\nLv %2").arg(name).arg(pokemon.minLevel); // Create label for legend
if (pokemon.minLevel != pokemon.maxLevel) QString label = QString("%1\nLv %2").arg(species).arg(entry.minLevel);
label.append(QString("-%1").arg(pokemon.maxLevel)); if (entry.minLevel != entry.maxLevel)
label.append(QString(" (%1%)").arg(percent * 100)); label.append(QString("-%1").arg(entry.maxLevel));
QPieSlice *slice = new QPieSlice(label, percent); // Add encounter chance data
//slice->setLabelPosition(QPieSlice::LabelInsideNormal); auto set = new QBarSet(label);
slice->setLabelVisible(); for (int i = 0; i < numGroups; i++)
series->append(slice); set->append(entry.values.value(i, 0));
// Insert bar set in order of total value
int i = 0;
for (; i < barSets.length(); i++){
if (barSets.at(i)->sum() > set->sum())
break;
}
barSets.insert(i, set);
} }
QChart *chart = new QChart(); auto series = new QHorizontalPercentBarSeries();
series->setLabelsVisible();
series->append(barSets);
auto chart = new QChart();
chart->addSeries(series); chart->addSeries(series);
chart->legend()->hide(); chart->setAnimationOptions(QChart::SeriesAnimations);
chart->legend()->setVisible(true);
chart->legend()->setShowToolTips(true);
chart->legend()->setAlignment(Qt::AlignBottom);
ui->chartView->setChart(chart); // TODO: Leaking old chart // X-axis is the values (percentages)
auto axisX = new QValueAxis();
chart->addAxis(axisX, Qt::AlignBottom);
series->attachAxis(axisX);
// TODO: Draw icons onto slices // Y-axis is the names of encounter groups (e.g. Old Rod, Good Rod...)
if (numGroups > 1) {
auto axisY = new QBarCategoryAxis();
axisY->setCategories(groupNames);
chart->addAxis(axisY, Qt::AlignLeft);
series->attachAxis(axisY);
}
delete ui->chartView->chart();
ui->chartView->setChart(chart);
} }