@@ -498,17 +537,12 @@ TEMPLATE = """
marker: { color: '#6abf69', line: { color: '#3e7c17', width: 1.5 } },
}], { ...layout_base, title: { text: 'Top Stores by Spend', font: { size: 16 } }, yaxis: { autorange: 'reversed' }, margin: { ...layout_base.margin, l: 180 } }, cfg);
- Plotly.newPlot('monthly', [{
- x: {{ month_labels|safe }}, y: {{ month_totals|safe }},
- type: 'bar',
- marker: { color: '#7ecce5', line: { color: '#4a9bb5', width: 1.5 } },
- }], { ...layout_base, title: { text: 'Monthly Spending', font: { size: 16 } } }, cfg);
Plotly.newPlot('hourly', [{
x: {{ hour_labels|safe }}, y: {{ hour_values|safe }},
type: 'bar',
marker: { color: '#f4c842', line: { color: '#c4a96a', width: 1.5 } },
- }], { ...layout_base, title: { text: 'Orders by Hour', font: { size: 16 } } }, cfg);
+ }], { ...layout_base, title: { text: 'Orders by Hour (EST)', font: { size: 16 } } }, cfg);
Plotly.newPlot('weekday', [{
x: {{ day_order|safe }}, y: {{ weekday_values|safe }},
@@ -571,6 +605,154 @@ TEMPLATE = """
inflationToggle.addEventListener('change', () => renderWrapped(yearSelect.value));
renderWrapped('All Time');
+ // Weight tracking
+ const WEIGHT_KEY = 'weakness_weight_log';
+ function getWeights() {
+ return JSON.parse(localStorage.getItem(WEIGHT_KEY) || '[]');
+ }
+ function saveWeights(w) {
+ localStorage.setItem(WEIGHT_KEY, JSON.stringify(w));
+ }
+ function addWeight() {
+ const dateInput = document.getElementById('weight-date');
+ const valInput = document.getElementById('weight-value');
+ const d = dateInput.value;
+ const v = parseFloat(valInput.value);
+ if (!d || isNaN(v)) return;
+ const weights = getWeights();
+ // Remove existing entry for same date
+ const filtered = weights.filter(w => w.date !== d);
+ filtered.push({ date: d, weight: v });
+ filtered.sort((a, b) => a.date.localeCompare(b.date));
+ saveWeights(filtered);
+ valInput.value = '';
+ renderWeightLog();
+ updateMonthlyChart();
+ }
+ document.getElementById('weight-csv-input').addEventListener('change', function(e) {
+ const file = e.target.files[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = function(ev) {
+ const lines = ev.target.result.trim().split(String.fromCharCode(10));
+ const weights = getWeights();
+ const existing = new Set(weights.map(w => w.date));
+ let added = 0;
+ for (const line of lines) {
+ const parts = line.split(',').map(s => s.trim());
+ if (parts.length < 2) continue;
+ const date = parts[0];
+ const val = parseFloat(parts[1]);
+ // Skip header rows or invalid data
+ if (!date.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) || isNaN(val)) continue;
+ // Overwrite existing entry for same date
+ const idx = weights.findIndex(w => w.date === date);
+ if (idx >= 0) weights[idx].weight = val;
+ else weights.push({ date, weight: val });
+ added++;
+ }
+ weights.sort((a, b) => a.date.localeCompare(b.date));
+ saveWeights(weights);
+ renderWeightLog();
+ updateMonthlyChart();
+ alert(`Imported ${added} weight entries.`);
+ };
+ reader.readAsText(file);
+ e.target.value = '';
+ });
+
+ function removeWeight(date) {
+ const weights = getWeights().filter(w => w.date !== date);
+ saveWeights(weights);
+ renderWeightLog();
+ updateMonthlyChart();
+ }
+ function renderWeightLog() {
+ const log = document.getElementById('weight-log');
+ const weights = getWeights();
+ if (!weights.length) {
+ log.innerHTML = '
No weight entries yet. Add your first one!
';
+ return;
+ }
+ log.innerHTML = weights.map(w =>
+ `
+ ${w.date}: ${w.weight} lbs
+
+
`
+ ).join('');
+ }
+
+ // Monthly chart with optional weight overlay
+ const monthLabels = {{ month_labels|safe }};
+ const monthTotals = {{ month_totals|safe }};
+
+ function updateMonthlyChart() {
+ const showWeight = document.getElementById('weight-overlay-toggle').checked;
+ const weights = getWeights();
+
+ // Aggregate weight by month (average per month)
+ const monthWeights = {};
+ const monthWeightCounts = {};
+ weights.forEach(w => {
+ const m = w.date.substring(0, 7); // YYYY-MM
+ if (!monthWeights[m]) { monthWeights[m] = 0; monthWeightCounts[m] = 0; }
+ monthWeights[m] += w.weight;
+ monthWeightCounts[m]++;
+ });
+
+ // Build unified month list when weight overlay is on
+ const weightMonths = Object.keys(monthWeights).sort();
+ const allMonths = showWeight && weights.length > 0
+ ? [...new Set([...monthLabels, ...weightMonths])].sort()
+ : monthLabels;
+
+ // Map spending to unified x-axis
+ const spendMap = {};
+ monthLabels.forEach((m, i) => { spendMap[m] = monthTotals[i]; });
+ const spendY = allMonths.map(m => spendMap[m] || 0);
+
+ const traces = [{
+ x: allMonths, y: spendY,
+ type: 'bar', name: 'Spending ($)',
+ marker: { color: '#7ecce5', line: { color: '#4a9bb5', width: 1.5 } },
+ }];
+
+ const layoutExtra = {};
+
+ if (showWeight && weights.length > 0) {
+ const weightY = allMonths.map(m => monthWeights[m] ? Math.round(monthWeights[m] / monthWeightCounts[m] * 10) / 10 : null);
+
+ traces.push({
+ x: allMonths, y: weightY,
+ type: 'scatter', mode: 'lines+markers', name: 'Weight (lbs)',
+ yaxis: 'y2',
+ line: { color: '#d4652a', width: 3 },
+ marker: { size: 7, color: '#d4652a' },
+ connectgaps: true,
+ });
+
+ layoutExtra.yaxis2 = {
+ title: 'Weight (lbs)',
+ overlaying: 'y', side: 'right',
+ showgrid: false,
+ font: { color: '#d4652a' },
+ titlefont: { color: '#d4652a' },
+ tickfont: { color: '#d4652a' },
+ };
+ }
+
+ Plotly.newPlot('monthly', traces, {
+ ...layout_base,
+ title: { text: 'Monthly Spending' + (showWeight && weights.length ? ' & Weight' : ''), font: { size: 16 } },
+ ...layoutExtra,
+ }, cfg);
+ }
+
+ document.getElementById('weight-overlay-toggle').addEventListener('change', updateMonthlyChart);
+ document.getElementById('weight-date').valueAsDate = new Date();
+ renderWeightLog();
+ updateMonthlyChart();
+
Plotly.newPlot('heatmap', [{
z: {{ heatmap_data|safe }}, y: {{ heatmap_days|safe }},
type: 'heatmap',