diff --git a/viz/app/item_page.js b/viz/app/item_page.js index fc20c64..86be3b1 100644 --- a/viz/app/item_page.js +++ b/viz/app/item_page.js @@ -102,6 +102,14 @@ function findMinPricesForSkuGroupInDb(obj, wantRealSkus, skuKeys, storeLabel) { return { liveMin, removedMin }; } +function lastFiniteFromEnd(arr) { + if (!Array.isArray(arr)) return null; + for (let i = arr.length - 1; i >= 0; i--) { + const v = arr[i]; + if (Number.isFinite(v)) return v; + } + return null; +} function computeSuggestedY(values, minRange) { const nums = values.filter((v) => Number.isFinite(v)); @@ -665,22 +673,41 @@ export async function renderItem($app, skuInput) { const span = (ySug.suggestedMax ?? 0) - (ySug.suggestedMin ?? 0); const step = niceStepAtLeast(MIN_STEP, span, MAX_TICKS); - const colorMap = buildStoreColorMap(series.map((s) => s.label)); - const datasets = series.map((s) => { + const todayKey = today; // you already computed this earlier + const labelsLen = labels.length; + + const seriesSorted = series + .map((s) => { + const todayVal = s.points.has(todayKey) ? s.points.get(todayKey) : null; + const lastVal = todayVal !== null ? todayVal : lastFiniteFromEnd(labels.map((d) => s.points.get(d))); + return { s, v: Number.isFinite(lastVal) ? lastVal : null }; + }) + .sort((a, b) => { + const av = a.v, bv = b.v; + if (av === null && bv === null) return a.s.label.localeCompare(b.s.label); + if (av === null) return 1; + if (bv === null) return -1; + if (av !== bv) return av - bv; + return a.s.label.localeCompare(b.s.label); + }) + .map((x) => x.s); + + const colorMap = buildStoreColorMap(seriesSorted.map((s) => s.label)); + + const datasets = seriesSorted.map((s) => { const base = storeColor(s.label, colorMap); const stroke = lighten(base, 0.25); - return { label: s.label, data: labels.map((d) => (s.points.has(d) ? s.points.get(d) : null)), spanGaps: false, tension: 0.15, - backgroundColor: base, borderColor: stroke, pointBackgroundColor: base, pointBorderColor: stroke, + borderWidth: datasetStrokeWidth(base), }; }); diff --git a/viz/app/stats_page.js b/viz/app/stats_page.js index 0ad6307..3920d2d 100644 --- a/viz/app/stats_page.js +++ b/viz/app/stats_page.js @@ -686,32 +686,51 @@ export async function renderStats($app) { return { q, minP, maxP }; } + function lastFiniteFromEnd(arr) { + if (!Array.isArray(arr)) return null; + for (let i = arr.length - 1; i >= 0; i--) { + const v = arr[i]; + if (Number.isFinite(v)) return v; + } + return null; + } + async function drawOrUpdateChart(series, yBounds) { const { labels, stores, seriesByStore } = series; const Chart = await ensureChartJs(); const canvas = document.getElementById("statsChart"); if (!canvas) return; - const colorMap = buildStoreColorMap(stores); - const datasets = stores.map((s) => { + const order = stores + .map((s) => ({ s, v: lastFiniteFromEnd(seriesByStore[s]) })) + .sort((a, b) => { + const av = a.v, bv = b.v; + if (av === null && bv === null) return displayStoreName(a.s).localeCompare(displayStoreName(b.s)); + if (av === null) return 1; + if (bv === null) return -1; + if (av !== bv) return av - bv; // cheapest (lowest index) first + return displayStoreName(a.s).localeCompare(displayStoreName(b.s)); + }) + .map((x) => x.s); + + const colorMap = buildStoreColorMap(order); + + const datasets = order.map((s) => { const base = storeColor(s, colorMap); const stroke = lighten(base, 0.25); - return { - label: displayStoreName(s), - data: Array.isArray(seriesByStore[s]) ? seriesByStore[s] : labels.map(() => null), - spanGaps: false, - tension: 0.15, - - backgroundColor: base, - borderColor: stroke, - pointBackgroundColor: base, - pointBorderColor: stroke, + label: displayStoreName(s), + data: Array.isArray(seriesByStore[s]) ? seriesByStore[s] : labels.map(() => null), + spanGaps: false, + tension: 0.15, + backgroundColor: base, + borderColor: stroke, + pointBackgroundColor: base, + pointBorderColor: stroke, }; - }); - - + }); + if (_chart) { _chart.data.labels = labels; _chart.data.datasets = datasets;