diff --git a/viz/app/item_page.js b/viz/app/item_page.js index 9574d0b..222c382 100644 --- a/viz/app/item_page.js +++ b/viz/app/item_page.js @@ -3,6 +3,7 @@ import { parsePriceToNumber, keySkuForRow, displaySku } from "./sku.js"; import { loadIndex } from "./state.js"; import { inferGithubOwnerRepo, githubListCommits, githubFetchFileAtSha, fetchJson } from "./api.js"; import { loadSkuRules } from "./mapping.js"; +import { storeColor, isWhite } from "./storeColors.js"; /* ---------------- Chart lifecycle ---------------- */ @@ -665,12 +666,23 @@ export async function renderItem($app, skuInput) { const span = (ySug.suggestedMax ?? 0) - (ySug.suggestedMin ?? 0); const step = niceStepAtLeast(MIN_STEP, span, MAX_TICKS); - const datasets = series.map((s) => ({ - label: s.label, - data: labels.map((d) => (s.points.has(d) ? s.points.get(d) : null)), - spanGaps: false, - tension: 0.15, - })); + const datasets = series.map((s) => { + const c = storeColor(s.label); // store label + + 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, + borderWidth: isWhite(c) ? 2 : 1.5, + }; + }); + const ctx = $canvas.getContext("2d"); CHART = new Chart(ctx, { diff --git a/viz/app/stats_page.js b/viz/app/stats_page.js index 0e957d9..8c581e1 100644 --- a/viz/app/stats_page.js +++ b/viz/app/stats_page.js @@ -5,6 +5,7 @@ import { githubFetchFileAtSha, githubListCommits, } from "./api.js"; +import { storeColor, isWhite } from "./storeColors.js"; let _chart = null; @@ -692,14 +693,23 @@ export async function renderStats($app) { const canvas = document.getElementById("statsChart"); if (!canvas) return; - const datasets = stores.map((s) => ({ - label: displayStoreName(s), - data: Array.isArray(seriesByStore[s]) - ? seriesByStore[s] - : labels.map(() => null), - spanGaps: false, - tension: 0.15, - })); + const datasets = stores.map((s) => { + const c = storeColor(s); // store key + + 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, + borderWidth: isWhite(c) ? 2 : 1.5, + }; + }); + if (_chart) { _chart.data.labels = labels; diff --git a/viz/app/storeColors.js b/viz/app/storeColors.js new file mode 100644 index 0000000..1785ebc --- /dev/null +++ b/viz/app/storeColors.js @@ -0,0 +1,44 @@ +function normalizeStoreId(s) { + return String(s || "") + .toLowerCase() + .replace(/[^a-z0-9]+/g, ""); + } + + const STORE_COLOR_OVERRIDES = { + strath: "#76B7FF", + bsw: "#E9DF7A", + kensingtonwinemarket: "#F2C200", + vessel: "#FFFFFF", + gullliquor: "#6B0F1A", + kegncork: "#111111", + legacyliquor: "#7B4A12", + vintagespirits: "#E34A2C", + + // aliases + gull: "#6B0F1A", + legacy: "#7B4A12", + vintage: "#E34A2C", + }; + + function hashToHue(str) { + let h = 2166136261; + for (let i = 0; i < str.length; i++) { + h ^= str.charCodeAt(i); + h = Math.imul(h, 16777619); + } + return (h >>> 0) % 360; + } + + export function storeColor(storeKeyOrLabel) { + const id = normalizeStoreId(storeKeyOrLabel); + const forced = STORE_COLOR_OVERRIDES[id]; + if (forced) return forced; + + const hue = hashToHue(id || "unknown"); + return `hsl(${hue} 65% 55%)`; + } + + export function isWhite(color) { + return String(color).toUpperCase() === "#FFFFFF"; + } + \ No newline at end of file