diff --git a/viz/app/item_page.js b/viz/app/item_page.js index 9fe2285..ee8fd41 100644 --- a/viz/app/item_page.js +++ b/viz/app/item_page.js @@ -668,20 +668,24 @@ export async function renderItem($app, skuInput) { const colorMap = buildStoreColorMap(series.map((s) => s.label)); const datasets = series.map((s) => { - const c = storeColor(s.label, colorMap); + 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, - borderColor: c, - backgroundColor: c, - pointBackgroundColor: c, - pointBorderColor: c, + backgroundColor: base, + borderColor: stroke, + pointBackgroundColor: base, + pointBorderColor: stroke, + borderWidth: datasetStrokeWidth(base), }; }); + const ctx = $canvas.getContext("2d"); CHART = new Chart(ctx, { type: "line", diff --git a/viz/app/stats_page.js b/viz/app/stats_page.js index c1f9b69..f20e24e 100644 --- a/viz/app/stats_page.js +++ b/viz/app/stats_page.js @@ -695,17 +695,20 @@ export async function renderStats($app) { const colorMap = buildStoreColorMap(stores); const datasets = stores.map((s) => { - const c = storeColor(s, colorMap); + 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, - borderColor: c, - backgroundColor: c, - pointBackgroundColor: c, - pointBorderColor: c, + backgroundColor: base, + borderColor: stroke, + pointBackgroundColor: base, + pointBorderColor: stroke, + borderWidth: datasetStrokeWidth(base), }; }); diff --git a/viz/app/storeColors.js b/viz/app/storeColors.js index 5e8092f..8765214 100644 --- a/viz/app/storeColors.js +++ b/viz/app/storeColors.js @@ -127,4 +127,39 @@ function normalizeId(s) { export function datasetPointRadius(color) { return isWhiteHex(color) ? 2.8 : 2.2; } + + function clamp(v, lo, hi) { + return Math.max(lo, Math.min(hi, v)); + } + + function hexToRgb(hex) { + const m = String(hex).replace("#", ""); + if (m.length !== 6) return null; + const n = parseInt(m, 16); + return { + r: (n >> 16) & 255, + g: (n >> 8) & 255, + b: n & 255, + }; + } + + function rgbToHex({ r, g, b }) { + const h = (x) => clamp(Math.round(x), 0, 255).toString(16).padStart(2, "0"); + return `#${h(r)}${h(g)}${h(b)}`; + } + + // lighten by mixing with white (amount 0–1) + export function lighten(hex, amount = 0.25) { + const rgb = hexToRgb(hex); + if (!rgb) return hex; + return rgbToHex({ + r: rgb.r + (255 - rgb.r) * amount, + g: rgb.g + (255 - rgb.g) * amount, + b: rgb.b + (255 - rgb.b) * amount, + }); + } + + export function datasetStrokeWidth(color) { + return String(color).toUpperCase() === "#FFFFFF" ? 2.5 : 1.5; + } \ No newline at end of file