diff --git a/forms/wildmonchart.ui b/forms/wildmonchart.ui index 88d3a590..21652f53 100644 --- a/forms/wildmonchart.ui +++ b/forms/wildmonchart.ui @@ -28,6 +28,9 @@ + + 0 + Species Distribution @@ -111,20 +114,47 @@ - - - Species + + + Individual Mode - - - - - + true - - QComboBox::NoInsert + + false + + + 4 + + + 4 + + + 4 + + + 4 + + + + + Species + + + + + + + true + + + QComboBox::NoInsert + + + + diff --git a/include/ui/wildmonchart.h b/include/ui/wildmonchart.h index 7c523355..59a23a80 100644 --- a/include/ui/wildmonchart.h +++ b/include/ui/wildmonchart.h @@ -28,16 +28,16 @@ private: QStringList groupNames; QMap tableIndexToGroupName; + struct LevelRange { + int min; + int max; + }; + QMap groupedLevelRanges; + struct Summary { double speciesFrequency = 0.0; QMap levelFrequencies; }; - - int tableMinLevel; - int tableMaxLevel; - - // GroupedData maps a group name ("old_rod", "good_rod"...) - // to any summarized data needed for the charts. typedef QMap GroupedData; QMap speciesToGroupedData; @@ -45,12 +45,14 @@ private: QStringList getSpeciesNames() const; double getSpeciesFrequency(const QString&, const QString&) const; QMap getLevelFrequencies(const QString &, const QString &) const; + LevelRange getLevelRange(const QString &species, const QString &groupName) const; bool usesGroupLabels() const; void clearTableData(); void readTable(); void createSpeciesDistributionChart(); void createLevelDistributionChart(); + QBarSet* createLevelDistributionBarSet(const QString &, const QString &, bool, double *); void stopChartAnimation(); }; diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index b7080aae..c6642002 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -4,18 +4,12 @@ #include "log.h" -// TODO: Make level range its own chart(s)? // TODO: Draw species icons below legend icons? // TODO: NoScrollComboBoxes +// TODO: Consistent species->color across charts static const QString baseWindowTitle = QString("Wild Pokémon Summary Charts"); -struct ChartData { - int minLevel; - int maxLevel; - QMap valueMap; // One value for each wild encounter group -}; - WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) : QWidget(parent), ui(new Ui::WildMonChart) @@ -24,6 +18,8 @@ WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) : setAttribute(Qt::WA_DeleteOnClose); setWindowFlags(Qt::Window); + // Changing these settings changes which level distribution chart is shown + connect(ui->groupBox_Species, &QGroupBox::clicked, this, &WildMonChart::createLevelDistributionChart); connect(ui->comboBox_Species, &QComboBox::currentTextChanged, this, &WildMonChart::createLevelDistributionChart); connect(ui->comboBox_Group, &QComboBox::currentTextChanged, this, &WildMonChart::createLevelDistributionChart); @@ -44,8 +40,7 @@ void WildMonChart::clearTableData() { this->groupNames.clear(); this->tableIndexToGroupName.clear(); this->speciesToGroupedData.clear(); - this->tableMinLevel = INT_MAX; - this->tableMaxLevel = INT_MIN; + this->groupedLevelRanges.clear(); setWindowTitle(baseWindowTitle); } @@ -91,13 +86,22 @@ void WildMonChart::readTable() { for (int level = pokemon.minLevel; level <= pokemon.maxLevel; level++) summary->levelFrequencies[level] += frequency / numLevels; - if (this->tableMinLevel > pokemon.minLevel) - this->tableMinLevel = pokemon.minLevel; - if (this->tableMaxLevel < pokemon.maxLevel) - this->tableMaxLevel = pokemon.maxLevel; + // Update level min/max for showing level distribution across a group + if (!this->groupedLevelRanges.contains(groupName)) { + LevelRange *levelRange = &this->groupedLevelRanges[groupName]; + levelRange->min = pokemon.minLevel; + levelRange->max = pokemon.maxLevel; + } else { + LevelRange *levelRange = &this->groupedLevelRanges[groupName]; + if (levelRange->min > pokemon.minLevel) + levelRange->min = pokemon.minLevel; + if (levelRange->max < pokemon.maxLevel) + levelRange->max = pokemon.maxLevel; + } } // Populate combo boxes + // TODO: Limit width const QSignalBlocker blocker1(ui->comboBox_Species); const QSignalBlocker blocker2(ui->comboBox_Group); ui->comboBox_Species->clear(); @@ -132,6 +136,20 @@ QMap WildMonChart::getLevelFrequencies(const QString &species, cons return this->speciesToGroupedData[species][groupName].levelFrequencies; } +WildMonChart::LevelRange WildMonChart::getLevelRange(const QString &species, const QString &groupName) const { + const QList levels = getLevelFrequencies(species, groupName).keys(); + + LevelRange range; + if (levels.isEmpty()) { + range.min = 0; + range.max = 0; + } else { + range.min = levels.first(); + range.max = levels.last(); + } + return range; +} + bool WildMonChart::usesGroupLabels() const { return this->groupNames.length() > 1; } @@ -155,15 +173,17 @@ void WildMonChart::createSpeciesDistributionChart() { // 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, species] (bool on, int i) { - QString text = on ? QString("%1 - %2%").arg(species).arg(set->at(i)) : ""; + QString text = on ? QString("%1 (%2%)").arg(species).arg(set->at(i)) : ""; QToolTip::showText(QCursor::pos(), text); }); } + // Set up series auto series = new QHorizontalPercentBarSeries(); series->setLabelsVisible(); series->append(barSets); + // Set up chart auto chart = new QChart(); chart->addSeries(series); chart->setAnimationOptions(QChart::SeriesAnimations); @@ -171,7 +191,7 @@ void WildMonChart::createSpeciesDistributionChart() { chart->legend()->setShowToolTips(true); chart->legend()->setAlignment(Qt::AlignBottom); - // X-axis is the values (percentages). We're already showing percentages on the bar, so we just display 0/50/100% + // 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->setLabelFormat("%u%%"); axisX->setTickCount(3); @@ -184,76 +204,102 @@ void WildMonChart::createSpeciesDistributionChart() { axisY->setCategories(this->groupNames); chart->addAxis(axisY, Qt::AlignLeft); series->attachAxis(axisY); - } else { - // TODO: y-axis has weird labels for a few frames on opening } // TODO: Delete old chart ui->chartView_SpeciesDistribution->setChart(chart); } -void WildMonChart::createLevelDistributionChart() { - // TODO: Handle combined chart - const QString species = ui->comboBox_Species->currentText(); - const QString groupName = ui->comboBox_Group->currentText(); - - const double speciesFrequency = getSpeciesFrequency(species, groupName); +QBarSet* WildMonChart::createLevelDistributionBarSet(const QString &species, const QString &groupName, bool individual, double *barMax) { + const double totalFrequency = individual ? getSpeciesFrequency(species, groupName) : 1.0; const QMap levelFrequencies = getLevelFrequencies(species, groupName); - const QList levels = levelFrequencies.keys(); - int minLevel = !levels.isEmpty() ? levels.first() : 0; - int maxLevel = !levels.isEmpty() ? levels.last() : 0; - if (maxLevel < minLevel) - return; - - double maxPercent = 0.0; - QStringList categories; auto set = new QBarSet(species); - for (int i = minLevel; i <= maxLevel; i++) { - double percent = (levelFrequencies.value(i, 0) / speciesFrequency) * 100; - if (maxPercent < percent) - maxPercent = percent; + LevelRange levelRange = individual ? getLevelRange(species, groupName) : this->groupedLevelRanges.value(groupName); + for (int i = levelRange.min; i <= levelRange.max; i++) { + double percent = levelFrequencies.value(i, 0) / totalFrequency * 100; + if (*barMax < percent) + *barMax = percent; set->append(percent); - categories.append(QString::number(i)); } - // Show level and % when hovering over a bar set. This covers some shortfalls in our ability to control the chart design. - connect(set, &QBarSet::hovered, [set, categories] (bool on, int i) { - QString text = on ? QString("Lv%1 - %2%").arg(categories.at(i)).arg(set->at(i)) : ""; + // Show data when hovering over a bar set. This covers some shortfalls in our ability to control the chart design. + connect(set, &QBarSet::hovered, [=] (bool on, int i) { + QString text = on ? QString("%1 Lv%2 (%3%)") + .arg(individual ? "" : species) + .arg(QString::number(i + levelRange.min)) + .arg(set->at(i)) + : ""; QToolTip::showText(QCursor::pos(), text); }); - auto series = new QBarSeries(); - series->append(set); - //series->setLabelsVisible(); + return set; +} +void WildMonChart::createLevelDistributionChart() { + const QString groupName = ui->comboBox_Group->currentText(); + + LevelRange levelRange; + double maxPercent = 0.0; + QList barSets; + + // Create bar sets + if (ui->groupBox_Species->isChecked()) { + // Species box is active, we just display data for the selected species. + const QString species = ui->comboBox_Species->currentText(); + barSets.append(createLevelDistributionBarSet(species, groupName, true, &maxPercent)); + levelRange = getLevelRange(species, groupName); + } else { + // Species box is inactive, we display data for all species in the table. + for (const auto species : getSpeciesNames()) + barSets.append(createLevelDistributionBarSet(species, groupName, false, &maxPercent)); + levelRange = this->groupedLevelRanges.value(groupName); + } + + // Set up chart auto chart = new QChart(); - chart->addSeries(series); //chart->setTitle(""); chart->setAnimationOptions(QChart::SeriesAnimations); chart->legend()->setVisible(true); chart->legend()->setShowToolTips(true); chart->legend()->setAlignment(Qt::AlignBottom); + // X-axis is the level range. QBarCategoryAxis *axisX = new QBarCategoryAxis(); - axisX->append(categories); + for (int i = levelRange.min; i <= levelRange.max; i++) + axisX->append(QString::number(i)); chart->addAxis(axisX, Qt::AlignBottom); - series->attachAxis(axisX); + // Y-axis is the % frequency. We round the max up to a multiple of 5. auto roundUp = [](int num, int multiple) { auto remainder = num % multiple; if (remainder == 0) return num; return num + multiple - remainder; }; - QValueAxis *axisY = new QValueAxis(); axisY->setMax(roundUp(qCeil(maxPercent), 5)); //axisY->setTickType(QValueAxis::TicksDynamic); //axisY->setTickInterval(5); axisY->setLabelFormat("%u%%"); chart->addAxis(axisY, Qt::AlignLeft); - series->attachAxis(axisY); + + // Set up series. Grouped mode uses a stacked bar series. + if (barSets.length() < 2) { + auto series = new QBarSeries(); + series->append(barSets); + series->attachAxis(axisX); + series->attachAxis(axisY); + //series->setLabelsVisible(); + chart->addSeries(series); + } else { + auto series = new QStackedBarSeries(); + series->append(barSets); + series->attachAxis(axisX); + series->attachAxis(axisY); + //series->setLabelsVisible(); + chart->addSeries(series); + } // TODO: Cache old chart ui->chartView_LevelDistribution->setChart(chart);