repos

Frame overview/watchlist charts to a consistent full Schwab day

3dd41340 by Isaac Bythewood · 1 month ago

Frame overview/watchlist charts to a consistent full Schwab day

The mini charts drifted: each card framed to wherever its real bars sat,
so a sparse card (BTC with one 8 PM bar, indexes that stopped at 5 PM)
zoomed differently than a full one and 7 AM never sat at the left edge.

Build every card on one identical 15-minute grid spanning the whole
Schwab day (7 AM to 8 PM ET), dropping each real bar onto its slot and
leaving every empty slot (before, after, and interior gaps) as
whitespace. Then pin the visible range with setVisibleLogicalRange over
the full grid instead of fitContent (which is data-dependent), and lock
it across resizes. Every card now shows 7 AM flush left and 8 PM flush
right regardless of how much real data it has.
modified frontend/static_src/home/scripts/hero.js
@@ -196,6 +196,10 @@ function attachChart(chartEl) {      timeVisible: true,      secondsVisible: false,      tickMarkFormatter: (t) => fmtAxisTime(t),      // We pin the visible range to the full Schwab-day grid ourselves (see      // drawSeries); keep that pin across resizes instead of letting the chart      // re-fit to wherever the real data happens to sit.      lockVisibleTimeRangeOnResize: true,    },    crosshair: {      mode: 1,
@@ -368,17 +372,30 @@ function drawSeries(entry, s) {    localization: { priceFormatter: (v) => fmtValue(v, s.unit), timeFormatter: (t) => fmtCrosshairTime(t) },  });  // Frame exactly the Schwab day [start_t, end_t]: pad whitespace (time only)  // before the first bar and after the last so a partial day plots from the left.  // Frame every card on ONE identical x-axis so the small multiples line up: a  // fixed 15-minute grid spanning the whole Schwab day [start_t, end_t] (7 AM–8 PM  // ET). Each real 15m bar drops onto its grid slot (Yahoo's 15m bars land exactly  // on this grid); every empty slot stays whitespace — not just before the first  // bar and after the last, but ALSO any interior gap where Yahoo skipped an  // illiquid pre-/after-hours bar. Filling those interior gaps is the point:  // otherwise lightweight-charts collapses consecutive bars into adjacent slots,  // so a card missing a few extended-hours bars ends up with fewer slots and  // fitContent() stretches it differently — the same clock time would sit at a  // different x on each card. With the full grid every card has the same slot  // count and the same time at the same horizontal position.  const pts = s.points;  const step = pts.length > 1 ? pts[1].t - pts[0].t : 900;  const GRID_STEP = 900; // 15 minutes, the intraday bar interval — shared by all cards  const slots = Math.max(0, Math.round((s.end_t - s.start_t) / GRID_STEP));  const valueAt = new Map();  for (const p of pts) {    const idx = Math.round((p.t - s.start_t) / GRID_STEP);    if (idx >= 0 && idx <= slots) valueAt.set(idx, p.v);  }  const data = [];  if (pts.length) {    for (let t = s.start_t; t < pts[0].t; t += step) data.push({ time: t });    for (const p of pts) data.push({ time: p.t, value: p.v });    for (let t = pts[pts.length - 1].t + step; t <= s.end_t; t += step) data.push({ time: t });  } else {    for (let t = s.start_t; t <= s.end_t; t += step) data.push({ time: t });  for (let idx = 0; idx <= slots; idx++) {    const t = s.start_t + idx * GRID_STEP;    const v = valueAt.get(idx);    data.push(v == null ? { time: t } : { time: t, value: v });  }  entry.series.setData(data);  entry.times = data.map((d) => ({ t: d.time, ext: isExtended(d.time) }));
@@ -392,7 +409,13 @@ function drawSeries(entry, s) {    axisLabelVisible: false,  });  entry.chart.timeScale().fitContent();  // Pin the visible range to the WHOLE grid, not fitContent(): fitContent frames  // to wherever the real values sit, so a sparse card (e.g. BTC with only a few  // recent bars) zooms in differently than a full one — the exact drift we're  // killing. With every card showing the identical logical range [0 .. last slot],  // 7 AM (slot 0) sits flush at the left edge and 8 PM (last slot) at the right on  // every card, regardless of how many real bars it has.  entry.chart.timeScale().setVisibleLogicalRange({ from: 0, to: data.length - 1 });  renderBands(entry);}