feat(market): OHLC hover readout on the candlestick chart

Candles had no hover (the line's close tooltip is gated off for them). Add a
candle-aware readout: the candle under the cursor gets a column highlight +
crosshair, and a small box shows its date and open/high/low/close (colored green
up / red down). Verified via a forced-hover render (date + O/H/L/C over the
highlighted candle). Line charts keep their existing single-price tooltip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 11:46:26 -05:00
parent 40e8128a30
commit a767702ff6

View File

@@ -1526,6 +1526,43 @@ static void mktDrawPriceChart(const MktCtx& cx)
dl->AddRectFilled(ImVec2(xc - bodyW * 0.5f, bT), ImVec2(xc + bodyW * 0.5f, bB), dl->AddRectFilled(ImVec2(xc - bodyW * 0.5f, bT), ImVec2(xc + bodyW * 0.5f, bB),
WithAlpha(col, 230), 1.0f); WithAlpha(col, 230), 1.0f);
} }
// Hover: highlight the candle under the cursor + an OHLC readout (date + open/high/low/close).
ImVec2 mp = ImGui::GetIO().MousePos;
if (mp.x >= plotLeft && mp.x <= plotRight && mp.y >= plotTop && mp.y <= plotBottom) {
int hi = (int)((mp.x - plotLeft) / slotW);
if (hi < 0) hi = 0;
if (hi >= cn) hi = cn - 1;
const auto& hc = candles[hi];
const float hx = plotLeft + ((float)hi + 0.5f) * slotW;
dl->AddRectFilled(ImVec2(hx - slotW * 0.5f, plotTop), ImVec2(hx + slotW * 0.5f, plotBottom),
IM_COL32(255, 255, 255, 12));
dl->AddLine(ImVec2(hx, plotTop), ImVec2(hx, plotBottom), IM_COL32(255, 255, 255, 45), 1.0f);
char when[40] = "";
std::time_t tt = hc.time;
if (std::tm* tmv = std::localtime(&tt))
std::strftime(when, sizeof(when), s_mkt.chartInterval <= 2 ? "%b %d %H:%M" : "%b %d, %Y", tmv);
char l2[80], l3[80];
snprintf(l2, sizeof(l2), "O %s H %s", FormatPrice(hc.open).c_str(), FormatPrice(hc.high).c_str());
snprintf(l3, sizeof(l3), "L %s C %s", FormatPrice(hc.low).c_str(), FormatPrice(hc.close).c_str());
const float lh = capFont->LegacySize;
const float pad = Layout::spacingSm();
const float gap = 3.0f * mktDp;
float tw = std::max(capFont->CalcTextSizeA(lh, FLT_MAX, 0, when).x,
std::max(capFont->CalcTextSizeA(lh, FLT_MAX, 0, l2).x,
capFont->CalcTextSizeA(lh, FLT_MAX, 0, l3).x));
float th = lh * 3 + gap * 2 + pad * 2;
float tipX = hx + 10.0f * mktDp;
if (tipX + tw + pad * 2 > plotRight) tipX = hx - tw - pad * 2 - 10.0f * mktDp;
tipX = std::max(plotLeft, tipX);
float tipY = plotTop + 4.0f * mktDp;
ImVec2 tMin(tipX, tipY), tMax(tipX + tw + pad * 2, tipY + th);
dl->AddRectFilled(tMin, tMax, IM_COL32(20, 20, 30, 235), 4.0f);
dl->AddRect(tMin, tMax, IM_COL32(255, 255, 255, 30), 4.0f, 0, 1.0f);
ImU32 cCol = (hc.close >= hc.open) ? Success() : Error();
dl->AddText(capFont, lh, ImVec2(tipX + pad, tipY + pad), OnSurface(), when);
dl->AddText(capFont, lh, ImVec2(tipX + pad, tipY + pad + lh + gap), cCol, l2);
dl->AddText(capFont, lh, ImVec2(tipX + pad, tipY + pad + 2 * lh + 2 * gap), cCol, l3);
}
} else { } else {
for (size_t i = 0; i < n; i++) { for (size_t i = 0; i < n; i++) {
float t = (n > 1) ? (float)i / (float)(n - 1) : 0.0f; float t = (n > 1) ? (float)i / (float)(n - 1) : 0.0f;