price graph styling fixes

This commit is contained in:
dgtlmoon
2026-06-22 00:44:32 +02:00
parent 1dae5b262f
commit 91a8b2caee
3 changed files with 36 additions and 24 deletions
@@ -18,7 +18,6 @@
const HEIGHT = 320;
const PAD = { top: 20, right: 16, bottom: 30, left: 52 };
const MAX_X_LABELS = 6; // keep the date axis uncluttered
const SMOOTHING = 0.28; // curve tension (Catmull-Rom default ~0.167; higher = curvier)
const DEFAULT_I18N = { in_stock: 'In stock', out_of_stock: 'Out of stock',
no_data: 'No price data available to graph yet.', load_error: 'Could not load price history.',
@@ -60,17 +59,6 @@
return (currency || '') + (Number.isInteger(n) ? n : n.toFixed(2));
}
// Catmull-Rom control points for segment p[i] -> p[i+1] -> "C c1x c1y c2x c2y x y".
function smoothSegment(pts, i) {
const p0 = pts[i - 1] || pts[i];
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = pts[i + 2] || pts[i + 1];
const c1x = p1.x + (p2.x - p0.x) * SMOOTHING, c1y = p1.y + (p2.y - p0.y) * SMOOTHING;
const c2x = p2.x - (p3.x - p1.x) * SMOOTHING, c2y = p2.y - (p3.y - p1.y) * SMOOTHING;
return `C ${c1x.toFixed(1)} ${c1y.toFixed(1)} ${c2x.toFixed(1)} ${c2y.toFixed(1)} ${p2.x.toFixed(1)} ${p2.y.toFixed(1)}`;
}
// Thin a large price series down to ~target points for drawing (you can't perceive more
// points than pixels). Always keeps the first, last and the true min/max points so the line
// still touches its extremes. The axis range + summary stay computed over the FULL data.
@@ -152,19 +140,19 @@
svg.appendChild(at);
}
// Line: one <path> per segment so each carries its own colour. A section is coloured by
// the stock state at the START of that interval (the state that held until the next check).
for (let i = 0; i < pts.length - 1; i++) {
const d = `M ${pts[i].x.toFixed(1)} ${pts[i].y.toFixed(1)} ${smoothSegment(pts, i)}`;
svg.appendChild(el('path', {
d: d, fill: 'none', stroke: stockColor(pts[i].d.in_stock),
'stroke-width': 2.5, 'stroke-linecap': 'round', 'stroke-linejoin': 'round'
}));
}
// Single neutral straight-line path connecting the points (no smoothing — a curve
// overshoots and reads as a price move that didn't happen). Stock status is shown by the
// DOT colours instead, not the line.
let linePath = '';
pts.forEach((p, i) => { linePath += (i === 0 ? 'M ' : ' L ') + p.x.toFixed(1) + ' ' + p.y.toFixed(1); });
svg.appendChild(el('path', {
class: 'rg-line', d: linePath, fill: 'none',
'stroke-width': 2, 'stroke-linecap': 'round', 'stroke-linejoin': 'round'
}));
// Dots, coloured by each point's own stock state.
// Dots, coloured by each point's own stock state (green = in stock, red = out of stock).
pts.forEach(p => {
svg.appendChild(el('circle', { cx: p.x.toFixed(1), cy: p.y.toFixed(1), r: 3, fill: stockColor(p.d.in_stock) }));
svg.appendChild(el('circle', { cx: p.x.toFixed(1), cy: p.y.toFixed(1), r: 3.2, fill: stockColor(p.d.in_stock) }));
});
// Sparse x-axis date labels: evenly spaced, always including both endpoints. The number
@@ -206,6 +194,14 @@
});
});
// Legend explaining the dot colours (the line is neutral; only dots indicate stock).
const $legend = $('<div class="rg-legend"></div>');
$legend.append($('<span class="rg-legend-item"></span>')
.append('<span class="rg-legend-dot in"></span>').append(document.createTextNode(' ' + (i18n.in_stock || 'In stock'))));
$legend.append($('<span class="rg-legend-item"></span>')
.append('<span class="rg-legend-dot out"></span>').append(document.createTextNode(' ' + (i18n.out_of_stock || 'Out of stock'))));
$container.append($legend);
// Footer stats: total recorded changes + average of the prices shown.
const avgPrice = prices.reduce((a, b) => a + b, 0) / prices.length;
$('<div class="rg-stats"></div>')
@@ -67,6 +67,22 @@ html[data-darkmode="true"] #restock-graph {
svg { display: block; max-width: 100%; }
.rg-axis { stroke: var(--color-border-notification, rgba(127, 127, 127, 0.4)); stroke-width: 1; }
.rg-label { fill: currentColor; opacity: 0.7; font-size: 12px; }
// Neutral single-colour price line; stock status is shown by the dot colours, not the line.
.rg-line { stroke: currentColor; opacity: 0.55; }
.rg-legend {
display: flex;
justify-content: center;
gap: 1.1rem;
margin-top: 0.4rem;
font-size: 0.78rem;
opacity: 0.85;
.rg-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; }
.rg-legend-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.rg-legend-dot.in { background: #1fa463; }
.rg-legend-dot.out { background: #e74c3c; }
}
.rg-stats {
margin-top: 0.35rem;
File diff suppressed because one or more lines are too long