fix: preserve month and drill-down state across reload
This commit is contained in:
parent
95fea028d8
commit
44a97c2ca8
125
app.js
125
app.js
@ -62,6 +62,8 @@ function saveToHistory(data, total, contextName, isInitial = false, path = []) {
|
|||||||
} else {
|
} else {
|
||||||
history.pushState(state, '');
|
history.pushState(state, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveMonthSelectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset history (called when changing months)
|
// Reset history (called when changing months)
|
||||||
@ -86,6 +88,7 @@ function navigateToHistoryState(state) {
|
|||||||
// Restore drill path and update mini-charts
|
// Restore drill path and update mini-charts
|
||||||
currentDrillPath = state.path ? [...state.path] : [];
|
currentDrillPath = state.path ? [...state.path] : [];
|
||||||
updateAllMonthPreviews();
|
updateAllMonthPreviews();
|
||||||
|
saveMonthSelectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go back in drill-down history
|
// Go back in drill-down history
|
||||||
@ -465,6 +468,7 @@ function getRussianMonthName(dateStr) {
|
|||||||
// Function to render the chart
|
// Function to render the chart
|
||||||
function renderChart(data) {
|
function renderChart(data) {
|
||||||
const sunburstData = transformToSunburst(data);
|
const sunburstData = transformToSunburst(data);
|
||||||
|
const restoredPath = [...currentDrillPath];
|
||||||
|
|
||||||
// Store the original data for resetting (module-level variable)
|
// Store the original data for resetting (module-level variable)
|
||||||
originalSunburstData = JSON.parse(JSON.stringify(sunburstData));
|
originalSunburstData = JSON.parse(JSON.stringify(sunburstData));
|
||||||
@ -961,8 +965,21 @@ function renderChart(data) {
|
|||||||
|
|
||||||
myChart.setOption(option);
|
myChart.setOption(option);
|
||||||
|
|
||||||
|
// 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
|
// Update HTML center label
|
||||||
updateCenterLabel(russianMonth, sunburstData.total);
|
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
|
// Add click handler for the center to go back in history
|
||||||
const zr = myChart.getZr();
|
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
|
// Ensure chart is properly sized after rendering
|
||||||
adjustChartSize();
|
adjustChartSize();
|
||||||
myChart.resize();
|
myChart.resize();
|
||||||
@ -1054,6 +1068,63 @@ let availableMonths = [];
|
|||||||
let currentMonthIndex = 0;
|
let currentMonthIndex = 0;
|
||||||
let selectedMonthIndices = new Set(); // Track all selected months for multi-selection
|
let selectedMonthIndices = new Set(); // Track all selected months for multi-selection
|
||||||
let monthDataCache = {}; // Cache for month data previews
|
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)
|
// Predefined colors for categories (same as in transformToSunburst)
|
||||||
const categoryColors = [
|
const categoryColors = [
|
||||||
@ -1451,11 +1522,20 @@ async function loadAvailableMonths() {
|
|||||||
monthList.appendChild(btn);
|
monthList.appendChild(btn);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load the most recent month by default
|
// 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;
|
currentMonthIndex = availableMonths.length - 1;
|
||||||
selectedMonthIndices.clear();
|
selectedMonthIndices.clear();
|
||||||
selectedMonthIndices.add(currentMonthIndex);
|
selectedMonthIndices.add(currentMonthIndex);
|
||||||
await selectMonth(currentMonthIndex);
|
await selectMonth(currentMonthIndex);
|
||||||
|
saveMonthSelectionState();
|
||||||
|
}
|
||||||
|
|
||||||
// Set up arrow button handlers
|
// Set up arrow button handlers
|
||||||
document.getElementById('prev-month').addEventListener('click', (event) => {
|
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
|
currentMonthIndex = index; // Update navigation index to the newly added month
|
||||||
}
|
}
|
||||||
await renderSelectedMonths();
|
await renderSelectedMonths();
|
||||||
|
saveMonthSelectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select a single month (normal click behavior)
|
// Select a single month (normal click behavior)
|
||||||
@ -1716,6 +1797,7 @@ async function selectSingleMonth(index) {
|
|||||||
selectedMonthIndices.add(index);
|
selectedMonthIndices.add(index);
|
||||||
currentMonthIndex = index;
|
currentMonthIndex = index;
|
||||||
await selectMonth(index);
|
await selectMonth(index);
|
||||||
|
saveMonthSelectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select interval from current month to target (Shift+click behavior)
|
// Select interval from current month to target (Shift+click behavior)
|
||||||
@ -1729,6 +1811,7 @@ async function selectMonthInterval(targetIndex) {
|
|||||||
}
|
}
|
||||||
currentMonthIndex = targetIndex;
|
currentMonthIndex = targetIndex;
|
||||||
await renderSelectedMonths();
|
await renderSelectedMonths();
|
||||||
|
saveMonthSelectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge transaction data from multiple months
|
// Merge transaction data from multiple months
|
||||||
@ -1781,6 +1864,7 @@ function generateSelectedMonthsLabel() {
|
|||||||
async function renderSelectedMonths() {
|
async function renderSelectedMonths() {
|
||||||
// Merge data from all selected months
|
// Merge data from all selected months
|
||||||
const mergedData = mergeMonthsData(selectedMonthIndices);
|
const mergedData = mergeMonthsData(selectedMonthIndices);
|
||||||
|
const savedPath = [...currentDrillPath];
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
updateMonthNavigator();
|
updateMonthNavigator();
|
||||||
@ -1801,16 +1885,35 @@ async function renderSelectedMonths() {
|
|||||||
|
|
||||||
// Reset history for the new selection
|
// Reset history for the new selection
|
||||||
resetHistory();
|
resetHistory();
|
||||||
|
|
||||||
|
// 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, []);
|
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
|
// Update the chart
|
||||||
if (option && option.series && option.series.data) {
|
if (option && option.series && option.series.data) {
|
||||||
option.series.data = sunburstData.data;
|
option.series.data = targetData;
|
||||||
|
|
||||||
myChart.setOption({
|
myChart.setOption({
|
||||||
series: [{
|
series: [{
|
||||||
type: 'sunburst',
|
type: 'sunburst',
|
||||||
data: sunburstData.data,
|
data: targetData,
|
||||||
layoutAnimation: true,
|
layoutAnimation: true,
|
||||||
animationDuration: 500,
|
animationDuration: 500,
|
||||||
animationEasing: 'cubicInOut'
|
animationEasing: 'cubicInOut'
|
||||||
@ -1820,8 +1923,8 @@ async function renderSelectedMonths() {
|
|||||||
silent: false
|
silent: false
|
||||||
});
|
});
|
||||||
|
|
||||||
updateCenterLabel(monthLabel, sunburstData.total, null);
|
updateCenterLabel(monthLabel, targetTotal, targetName);
|
||||||
setupHoverEvents(sunburstData, null);
|
setupHoverEvents({ total: targetTotal, data: targetData }, targetName);
|
||||||
updateAllMonthPreviews();
|
updateAllMonthPreviews();
|
||||||
} else {
|
} else {
|
||||||
// Initial render
|
// Initial render
|
||||||
@ -1833,6 +1936,8 @@ async function renderSelectedMonths() {
|
|||||||
if (activeBtn) {
|
if (activeBtn) {
|
||||||
activeBtn.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
activeBtn.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveMonthSelectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select and load a specific month
|
// Select and load a specific month
|
||||||
@ -1840,7 +1945,7 @@ async function selectMonth(index) {
|
|||||||
const month = availableMonths[index];
|
const month = availableMonths[index];
|
||||||
|
|
||||||
// If clicking on the already-selected month while drilled down, reset to root
|
// 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();
|
goToRoot();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1934,6 +2039,8 @@ async function selectMonth(index) {
|
|||||||
if (activeBtn) {
|
if (activeBtn) {
|
||||||
activeBtn.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
activeBtn.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveMonthSelectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update month navigator UI state
|
// Update month navigator UI state
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user