Summary chart to horizontal percent bar chart
This commit is contained in:
parent
b1814e0e3f
commit
2c65c22b30
5 changed files with 107 additions and 59 deletions
|
@ -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;
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue