diff --git a/app.js b/app.js index 0fb8ade..2c9f815 100644 --- a/app.js +++ b/app.js @@ -62,6 +62,8 @@ function saveToHistory(data, total, contextName, isInitial = false, path = []) { } else { history.pushState(state, ''); } + + saveMonthSelectionState(); } // Reset history (called when changing months) @@ -86,6 +88,7 @@ function navigateToHistoryState(state) { // Restore drill path and update mini-charts currentDrillPath = state.path ? [...state.path] : []; updateAllMonthPreviews(); + saveMonthSelectionState(); } // Go back in drill-down history @@ -465,6 +468,7 @@ function getRussianMonthName(dateStr) { // Function to render the chart function renderChart(data) { const sunburstData = transformToSunburst(data); + const restoredPath = [...currentDrillPath]; // Store the original data for resetting (module-level variable) originalSunburstData = JSON.parse(JSON.stringify(sunburstData)); @@ -961,8 +965,21 @@ function renderChart(data) { myChart.setOption(option); - // Update HTML center label - updateCenterLabel(russianMonth, sunburstData.total); + // Try to restore drill-down path on initial render (e.g., after page reload) + const restoredState = navigateToPath(sunburstData, restoredPath); + if (restoredState) { + option.series.data = restoredState.data; + myChart.setOption(option, { replaceMerge: ['series'] }); + saveToHistory(restoredState.data, restoredState.total, restoredState.contextName, false, restoredState.path); + updateCenterLabel(russianMonth, restoredState.total, restoredState.contextName); + setupHoverEvents({ total: restoredState.total, data: restoredState.data }, restoredState.contextName); + updateAllMonthPreviews(); + } else { + // Update HTML center label + updateCenterLabel(russianMonth, sunburstData.total); + // Set up hover events for the details box + setupHoverEvents(sunburstData); + } // Add click handler for the center to go back in history const zr = myChart.getZr(); @@ -984,9 +1001,6 @@ function renderChart(data) { } }); - // Set up hover events for the details box - setupHoverEvents(sunburstData); - // Ensure chart is properly sized after rendering adjustChartSize(); myChart.resize(); @@ -1054,6 +1068,63 @@ let availableMonths = []; let currentMonthIndex = 0; let selectedMonthIndices = new Set(); // Track all selected months for multi-selection let monthDataCache = {}; // Cache for month data previews +const MONTH_SELECTION_STORAGE_KEY = 'visual-spending:month-selection'; + +function saveMonthSelectionState() { + try { + const selectedMonths = [...selectedMonthIndices] + .map(index => availableMonths[index]) + .filter(Boolean); + const currentMonth = availableMonths[currentMonthIndex] || null; + + localStorage.setItem(MONTH_SELECTION_STORAGE_KEY, JSON.stringify({ + currentMonth, + selectedMonths, + drillPath: [...currentDrillPath] + })); + } catch (error) { + // Ignore storage failures (private mode, disabled storage, etc.) + } +} + +function restoreMonthSelectionState() { + try { + const raw = localStorage.getItem(MONTH_SELECTION_STORAGE_KEY); + if (!raw) return false; + + const saved = JSON.parse(raw); + if (!saved || !Array.isArray(saved.selectedMonths) || saved.selectedMonths.length === 0) { + return false; + } + + const restoredIndices = saved.selectedMonths + .map(month => availableMonths.indexOf(month)) + .filter(index => index >= 0); + + if (restoredIndices.length === 0) { + return false; + } + + selectedMonthIndices = new Set(restoredIndices); + + const restoredCurrentIndex = availableMonths.indexOf(saved.currentMonth); + if (restoredCurrentIndex >= 0 && selectedMonthIndices.has(restoredCurrentIndex)) { + currentMonthIndex = restoredCurrentIndex; + } else { + currentMonthIndex = Math.max(...selectedMonthIndices); + } + + if (Array.isArray(saved.drillPath)) { + currentDrillPath = [...saved.drillPath]; + } else { + currentDrillPath = []; + } + + return true; + } catch (error) { + return false; + } +} // Predefined colors for categories (same as in transformToSunburst) const categoryColors = [ @@ -1451,11 +1522,20 @@ async function loadAvailableMonths() { monthList.appendChild(btn); }); - // Load the most recent month by default - currentMonthIndex = availableMonths.length - 1; - selectedMonthIndices.clear(); - selectedMonthIndices.add(currentMonthIndex); - await selectMonth(currentMonthIndex); + // Restore previous month selection when possible, otherwise default to latest month + if (restoreMonthSelectionState()) { + if (selectedMonthIndices.size > 1) { + await renderSelectedMonths(); + } else { + await selectMonth(currentMonthIndex); + } + } else { + currentMonthIndex = availableMonths.length - 1; + selectedMonthIndices.clear(); + selectedMonthIndices.add(currentMonthIndex); + await selectMonth(currentMonthIndex); + saveMonthSelectionState(); + } // Set up arrow button handlers document.getElementById('prev-month').addEventListener('click', (event) => { @@ -1708,6 +1788,7 @@ async function toggleMonthSelection(index) { currentMonthIndex = index; // Update navigation index to the newly added month } await renderSelectedMonths(); + saveMonthSelectionState(); } // Select a single month (normal click behavior) @@ -1716,6 +1797,7 @@ async function selectSingleMonth(index) { selectedMonthIndices.add(index); currentMonthIndex = index; await selectMonth(index); + saveMonthSelectionState(); } // Select interval from current month to target (Shift+click behavior) @@ -1729,6 +1811,7 @@ async function selectMonthInterval(targetIndex) { } currentMonthIndex = targetIndex; await renderSelectedMonths(); + saveMonthSelectionState(); } // Merge transaction data from multiple months @@ -1781,6 +1864,7 @@ function generateSelectedMonthsLabel() { async function renderSelectedMonths() { // Merge data from all selected months const mergedData = mergeMonthsData(selectedMonthIndices); + const savedPath = [...currentDrillPath]; // Update UI updateMonthNavigator(); @@ -1801,16 +1885,35 @@ async function renderSelectedMonths() { // Reset history for the new selection resetHistory(); - saveToHistory(sunburstData.data, sunburstData.total, null, true, []); + + // Try to restore the same drill-down path in merged view + const navigatedState = navigateToPath(sunburstData, savedPath); + + let targetData, targetTotal, targetName, targetPath; + + if (navigatedState) { + saveToHistory(sunburstData.data, sunburstData.total, null, true, []); + targetData = navigatedState.data; + targetTotal = navigatedState.total; + targetName = navigatedState.contextName; + targetPath = navigatedState.path; + saveToHistory(targetData, targetTotal, targetName, false, targetPath); + } else { + targetData = sunburstData.data; + targetTotal = sunburstData.total; + targetName = null; + targetPath = []; + saveToHistory(targetData, targetTotal, targetName, true, targetPath); + } // Update the chart if (option && option.series && option.series.data) { - option.series.data = sunburstData.data; + option.series.data = targetData; myChart.setOption({ series: [{ type: 'sunburst', - data: sunburstData.data, + data: targetData, layoutAnimation: true, animationDuration: 500, animationEasing: 'cubicInOut' @@ -1820,8 +1923,8 @@ async function renderSelectedMonths() { silent: false }); - updateCenterLabel(monthLabel, sunburstData.total, null); - setupHoverEvents(sunburstData, null); + updateCenterLabel(monthLabel, targetTotal, targetName); + setupHoverEvents({ total: targetTotal, data: targetData }, targetName); updateAllMonthPreviews(); } else { // Initial render @@ -1833,6 +1936,8 @@ async function renderSelectedMonths() { if (activeBtn) { activeBtn.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); } + + saveMonthSelectionState(); } // Select and load a specific month @@ -1840,7 +1945,7 @@ async function selectMonth(index) { const month = availableMonths[index]; // If clicking on the already-selected month while drilled down, reset to root - if (index === currentMonthIndex && currentDrillPath.length > 0) { + if (index === currentMonthIndex && currentDrillPath.length > 0 && drillDownHistory.length > 0) { goToRoot(); return; } @@ -1934,6 +2039,8 @@ async function selectMonth(index) { if (activeBtn) { activeBtn.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); } + + saveMonthSelectionState(); } // Update month navigator UI state