Skip to content

Commit 9b0930b

Browse files
committed
feat(qt): add budget donut chart to proposal info view
1 parent 35e87a8 commit 9b0930b

File tree

2 files changed

+61
-8
lines changed

2 files changed

+61
-8
lines changed

src/qt/forms/proposalinfo.ui

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,31 @@
350350
</layout>
351351
</widget>
352352
</item>
353+
<item>
354+
<widget class="DonutChart" name="budgetChart">
355+
<property name="minimumSize">
356+
<size>
357+
<width>150</width>
358+
<height>150</height>
359+
</size>
360+
</property>
361+
<property name="sizePolicy">
362+
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
363+
<horstretch>1</horstretch>
364+
<verstretch>0</verstretch>
365+
</sizepolicy>
366+
</property>
367+
</widget>
368+
</item>
353369
</layout>
354370
</widget>
371+
<customwidgets>
372+
<customwidget>
373+
<class>DonutChart</class>
374+
<extends>QWidget</extends>
375+
<header>qt/donutchart.h</header>
376+
</customwidget>
377+
</customwidgets>
355378
<resources/>
356379
<connections/>
357380
</ui>

src/qt/proposalinfo.cpp

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <qt/bitcoinunits.h>
1414
#include <qt/clientfeeds.h>
1515
#include <qt/clientmodel.h>
16+
#include <qt/donutchart.h>
1617
#include <qt/guiutil.h>
1718
#include <qt/guiutil_font.h>
1819
#include <qt/optionsmodel.h>
@@ -162,10 +163,12 @@ void ProposalInfo::updateProposalInfo()
162163
ui->labelWeightedVotes->setText(tr("N/A"));
163164
#endif // ENABLE_WALLET
164165

165-
// Proposals section
166+
// Proposals section with budget chart
166167
const BitcoinUnit display_unit{m_client_model->getOptionsModel()->getDisplayUnit()};
167168
const CAmount budget_avail{gov_info.governancebudget};
169+
const QColor base_blue{GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BLUE).darker(160)};
168170

171+
// Collect passing proposals sorted by vote count
169172
std::vector<std::shared_ptr<Proposal>> passing_proposals;
170173
uint16_t proposals_fail{0}, proposals_total{0};
171174
for (const auto& prop : data_pr->m_proposals) {
@@ -176,28 +179,55 @@ void ProposalInfo::updateProposalInfo()
176179
proposals_fail++;
177180
}
178181
}
182+
std::sort(passing_proposals.begin(), passing_proposals.end(),
183+
[](const std::shared_ptr<Proposal>& a, const std::shared_ptr<Proposal>& b) {
184+
return a->getAbsoluteYesCount() > b->getAbsoluteYesCount();
185+
});
179186

180-
CAmount budget_requested{0}, unfunded_shortfall{0};
187+
CAmount budget_funded{0}, unfunded_shortfall{0};
181188
uint16_t unfunded_count{0};
182-
for (const auto& prop : passing_proposals) {
189+
std::vector<DonutChart::Slice> chart_slices{};
190+
191+
for (size_t idx{0}; idx < passing_proposals.size(); idx++) {
192+
const auto& prop{passing_proposals[idx]};
183193
const CAmount proposed_amount{prop->paymentAmount()};
184-
budget_requested += proposed_amount;
185-
if (data_pr->m_fundable_hashes.count(prop->objHash()) == 0) {
194+
if (data_pr->m_fundable_hashes.count(prop->objHash()) > 0) {
195+
budget_funded += proposed_amount;
196+
} else {
186197
unfunded_count++;
187198
unfunded_shortfall += proposed_amount;
188199
}
200+
const double slice_pct{budget_avail <= 0
201+
? 0.0
202+
: (static_cast<double>(proposed_amount) / static_cast<double>(budget_avail)) * 100.0};
203+
chart_slices.emplace_back(DonutChart::Slice{
204+
static_cast<double>(proposed_amount),
205+
(idx == 0) ? base_blue : base_blue.lighter(100 + static_cast<int>(idx * 20)),
206+
prop->title().isEmpty() ? tr("Proposal %1").arg(idx + 1) : prop->title(),
207+
GUIUtil::formatAmount(display_unit, proposed_amount),
208+
tr("%1% of budget").arg(slice_pct, 0, 'f', 1)
209+
});
189210
}
190211

191212
ui->labelProposalCount->setText(QString::number(proposals_total));
192213
ui->labelPassingProposals->setText(QString::number(passing_proposals.size()));
193214
ui->labelFailingProposals->setText(QString::number(proposals_fail));
194215

216+
const double alloc_pct_chart{budget_avail <= 0
217+
? 0.0
218+
: (static_cast<double>(budget_funded) / static_cast<double>(budget_avail)) * 100.0};
219+
DonutChart::CenterText default_text{
220+
GUIUtil::formatAmount(display_unit, budget_funded),
221+
tr("/ %1").arg(GUIUtil::formatAmount(display_unit, budget_avail)),
222+
tr("%1% allocated").arg(alloc_pct_chart, 0, 'f', 1)
223+
};
224+
ui->budgetChart->setData(std::move(chart_slices), static_cast<double>(budget_avail), std::move(default_text));
225+
195226
if (budget_avail > 0) {
196-
const double alloc_pct{(static_cast<double>(budget_requested) / static_cast<double>(budget_avail)) * 100.0};
197227
ui->labelBudgetAllocated->setText(
198-
tr("%1 / %2 (%3%)").arg(GUIUtil::formatAmount(display_unit, budget_requested, /*is_signed=*/false, /*truncate=*/2))
228+
tr("%1 / %2 (%3%)").arg(GUIUtil::formatAmount(display_unit, budget_funded, /*is_signed=*/false, /*truncate=*/2))
199229
.arg(GUIUtil::formatAmount(display_unit, budget_avail, /*is_signed=*/false, /*truncate=*/2))
200-
.arg(alloc_pct, 0, 'f', 2));
230+
.arg(alloc_pct_chart, 0, 'f', 2));
201231
} else {
202232
ui->labelBudgetAllocated->setText(tr("N/A"));
203233
}

0 commit comments

Comments
 (0)