feat(market): optional price-trend sparkline per portfolio group
Add a per-group "Sparkline" toggle (live-market bases only, like 24h). When on, the group card draws a faint price-trend line in its lower band — the group's value tracks the DRGX price, so it plots the market price_history (normalized), colored green/red by overall direction. Behind the text so amounts stay readable. The editor's live preview reflects it too. Persisted as show_sparkline in settings.json; wired through PortfolioEntry + the editor working state + the pfDrawSparkline() helper shared by card+preview. Full-node + Lite build clean; ctest 1/1; hygiene clean. Rendering change — needs a screenshot check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -330,6 +330,8 @@ bool Settings::load(const std::string& path)
|
||||
entry.showValue = e["show_value"].get<bool>();
|
||||
if (e.contains("show_24h") && e["show_24h"].is_boolean())
|
||||
entry.show24h = e["show_24h"].get<bool>();
|
||||
if (e.contains("show_sparkline") && e["show_sparkline"].is_boolean())
|
||||
entry.showSparkline = e["show_sparkline"].get<bool>();
|
||||
if (!entry.label.empty()) portfolio_entries_.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
@@ -487,6 +489,7 @@ bool Settings::save(const std::string& path)
|
||||
entry["show_drgx"] = e.showDrgx;
|
||||
entry["show_value"] = e.showValue;
|
||||
entry["show_24h"] = e.show24h;
|
||||
entry["show_sparkline"] = e.showSparkline;
|
||||
j["portfolio_entries"].push_back(std::move(entry));
|
||||
}
|
||||
j["font_scale"] = font_scale_;
|
||||
|
||||
@@ -89,6 +89,7 @@ public:
|
||||
bool showDrgx = true; // show the DRGX amount on the card
|
||||
bool showValue = true; // show the converted/fiat value on the card
|
||||
bool show24h = false; // show the 24h % change (live-market bases only)
|
||||
bool showSparkline = false; // show a price-trend sparkline (live-market bases only)
|
||||
};
|
||||
|
||||
// Theme
|
||||
|
||||
@@ -110,6 +110,26 @@ static char s_pf_manual_ccy[12] = "USD";
|
||||
static bool s_pf_show_drgx = true;
|
||||
static bool s_pf_show_value = true;
|
||||
static bool s_pf_show_24h = false;
|
||||
static bool s_pf_show_sparkline = false;
|
||||
|
||||
// A group's value trend tracks the DRGX price, so a group sparkline plots the price history
|
||||
// (normalized) across a rect. Colored by overall direction. Only meaningful for market bases.
|
||||
static void pfDrawSparkline(ImDrawList* dl, ImVec2 mn, ImVec2 mx,
|
||||
const std::vector<double>& hist, ImU32 col)
|
||||
{
|
||||
int n = (int)hist.size();
|
||||
if (n < 2) return;
|
||||
double lo = *std::min_element(hist.begin(), hist.end());
|
||||
double hi = *std::max_element(hist.begin(), hist.end());
|
||||
if (hi <= lo) hi = lo + 1e-9;
|
||||
float w = mx.x - mn.x, h = mx.y - mn.y;
|
||||
std::vector<ImVec2> pts(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
float t = (n > 1) ? (float)i / (float)(n - 1) : 0.0f;
|
||||
pts[i] = ImVec2(mn.x + t * w, mx.y - (float)((hist[i] - lo) / (hi - lo)) * h);
|
||||
}
|
||||
dl->AddPolyline(pts.data(), n, col, ImDrawFlags_None, 1.2f);
|
||||
}
|
||||
|
||||
// Build the right-hand amount strings for a group card / preview from its price-data config.
|
||||
struct PfDisplay {
|
||||
@@ -177,6 +197,7 @@ static void PortfolioBeginEdit(App* app, int index)
|
||||
s_pf_show_drgx = src.showDrgx;
|
||||
s_pf_show_value = src.showValue;
|
||||
s_pf_show_24h = src.show24h;
|
||||
s_pf_show_sparkline = src.showSparkline;
|
||||
} else {
|
||||
s_pf_label[0] = '\0';
|
||||
s_pf_addrs.clear();
|
||||
@@ -188,6 +209,7 @@ static void PortfolioBeginEdit(App* app, int index)
|
||||
s_pf_show_drgx = true;
|
||||
s_pf_show_value = true;
|
||||
s_pf_show_24h = false;
|
||||
s_pf_show_sparkline = false;
|
||||
}
|
||||
s_pf_search[0] = '\0';
|
||||
s_pf_type_filter = 0;
|
||||
@@ -280,6 +302,14 @@ static void RenderPortfolioEditor(App* app)
|
||||
pdl->AddRectFilled(ImVec2(bx, pMin.y + ppad * 0.6f),
|
||||
ImVec2(bx + 3.0f, pMax.y - ppad * 0.6f), accent, 2.0f);
|
||||
}
|
||||
if (s_pf_show_sparkline && (s_pf_price_basis == 0 || s_pf_price_basis == 1) &&
|
||||
state.market.price_history.size() >= 2) {
|
||||
const auto& h = state.market.price_history;
|
||||
ImU32 spCol = WithAlpha(h.back() >= h.front() ? Success() : Error(), 80);
|
||||
ImVec2 spMin(pMin.x + ppad + (accent ? 6.0f : 0.0f), pMin.y + ph * 0.46f);
|
||||
ImVec2 spMax(pMax.x - ppad, pMax.y - ppad * 0.6f);
|
||||
pfDrawSparkline(pdl, spMin, spMax, h, spCol);
|
||||
}
|
||||
float rowY = pMin.y + ppad, nameX = pMin.x + ppad + (accent ? 6.0f : 0.0f);
|
||||
if (!s_pf_icon.empty()) {
|
||||
material::project_icons::drawByName(pdl, s_pf_icon,
|
||||
@@ -418,6 +448,10 @@ static void RenderPortfolioEditor(App* app)
|
||||
ImGui::BeginDisabled(!(s_pf_price_basis == 0 || s_pf_price_basis == 1)); // 24h: live-market only
|
||||
ImGui::Checkbox(TR("portfolio_show_24h"), &s_pf_show_24h);
|
||||
ImGui::EndDisabled();
|
||||
// Sparkline on its own line (also live-market only).
|
||||
ImGui::BeginDisabled(!(s_pf_price_basis == 0 || s_pf_price_basis == 1));
|
||||
ImGui::Checkbox(TR("portfolio_show_sparkline"), &s_pf_show_sparkline);
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
@@ -629,6 +663,7 @@ static void RenderPortfolioEditor(App* app)
|
||||
e.showDrgx = s_pf_show_drgx;
|
||||
e.showValue = s_pf_show_value;
|
||||
e.show24h = s_pf_show_24h;
|
||||
e.showSparkline = s_pf_show_sparkline;
|
||||
if (s_pf_editing >= 0 && s_pf_editing < (int)entries.size()) entries[s_pf_editing] = e;
|
||||
else entries.push_back(e);
|
||||
persist(entries);
|
||||
@@ -1298,6 +1333,17 @@ void RenderMarketTab(App* app)
|
||||
ImVec2(bx + 3.0f, gcMax.y - pfCardPad * 0.6f), accent, 2.0f);
|
||||
}
|
||||
|
||||
// Price-trend sparkline (faint, in the lower band, behind the text) for
|
||||
// live-market groups. The group's value tracks the DRGX price, so plot its history.
|
||||
if (e.showSparkline && (e.priceBasis == 0 || e.priceBasis == 1) &&
|
||||
market.price_history.size() >= 2) {
|
||||
const auto& h = market.price_history;
|
||||
ImU32 spCol = WithAlpha(h.back() >= h.front() ? Success() : Error(), 80);
|
||||
ImVec2 spMin(gcMin.x + pfCardPad + (accent ? 6.0f : 0.0f), gcMin.y + pfCardH * 0.46f);
|
||||
ImVec2 spMax(gcMax.x - pfCardPad, gcMax.y - pfCardPad * 0.6f);
|
||||
pfDrawSparkline(dl, spMin, spMax, h, spCol);
|
||||
}
|
||||
|
||||
double bal = data::SumPortfolioBalance(e.addresses, state.addresses);
|
||||
float rowY = gcMin.y + pfCardPad;
|
||||
float gcRight = gcMax.x - pfCardPad;
|
||||
|
||||
@@ -1134,6 +1134,7 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["portfolio_show"] = "Show:";
|
||||
strings_["portfolio_show_value"] = "Value";
|
||||
strings_["portfolio_show_24h"] = "24h";
|
||||
strings_["portfolio_show_sparkline"] = "Sparkline";
|
||||
strings_["portfolio_appearance"] = "APPEARANCE";
|
||||
strings_["portfolio_addresses_hdr"] = "ADDRESSES";
|
||||
strings_["portfolio_search"] = "Search addresses\xE2\x80\xA6";
|
||||
|
||||
Reference in New Issue
Block a user