Add browser history integration for drill-down navigation
- Push drill-down states to browser history via history.pushState() - Listen for popstate event to handle browser back/forward - Mouse back/forward buttons and browser buttons now navigate drill-down - Click center still works (calls history.back()) - Reset details panel when mouse leaves chart sector Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
08db280f84
commit
58082e02f4
109
app.js
109
app.js
@ -4,6 +4,82 @@ const myChart = echarts.init(chartDom);
|
||||
let option;
|
||||
let originalSunburstData = null; // Stores the original data for the current month (for reset on center click)
|
||||
|
||||
// Drill-down history for back/forward navigation
|
||||
let drillDownHistory = [];
|
||||
let historyIndex = -1;
|
||||
|
||||
// Save current chart state to history
|
||||
function saveToHistory(data, total, contextName, isInitial = false) {
|
||||
// Remove any forward history when drilling down from middle of history
|
||||
if (historyIndex < drillDownHistory.length - 1) {
|
||||
drillDownHistory = drillDownHistory.slice(0, historyIndex + 1);
|
||||
}
|
||||
drillDownHistory.push({ data, total, contextName });
|
||||
historyIndex = drillDownHistory.length - 1;
|
||||
|
||||
// Push to browser history (use replaceState for initial state)
|
||||
const state = { drillDown: historyIndex };
|
||||
if (isInitial) {
|
||||
history.replaceState(state, '');
|
||||
} else {
|
||||
history.pushState(state, '');
|
||||
}
|
||||
}
|
||||
|
||||
// Reset history (called when changing months)
|
||||
function resetHistory() {
|
||||
drillDownHistory = [];
|
||||
historyIndex = -1;
|
||||
}
|
||||
|
||||
// Navigate to a specific history state
|
||||
function navigateToHistoryState(state) {
|
||||
const russianMonth = getRussianMonthName(document.getElementById('month-select').value);
|
||||
option.series.data = state.data;
|
||||
|
||||
if (state.contextName) {
|
||||
option.graphic.elements[0].style.text = `${russianMonth}\n${state.contextName}\n${state.total.toFixed(0).toLocaleString()} ₽`;
|
||||
} else {
|
||||
option.graphic.elements[0].style.text = russianMonth + '\n' + state.total.toFixed(0).toLocaleString() + ' ₽';
|
||||
}
|
||||
|
||||
myChart.setOption(option, { replaceMerge: ['series'] });
|
||||
setupHoverEvents({ total: state.total, data: state.data }, state.contextName);
|
||||
}
|
||||
|
||||
// Go back in drill-down history
|
||||
function navigateBack() {
|
||||
if (historyIndex > 0) {
|
||||
historyIndex--;
|
||||
navigateToHistoryState(drillDownHistory[historyIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
// Go forward in drill-down history
|
||||
function navigateForward() {
|
||||
if (historyIndex < drillDownHistory.length - 1) {
|
||||
historyIndex++;
|
||||
navigateToHistoryState(drillDownHistory[historyIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for browser back/forward via popstate
|
||||
window.addEventListener('popstate', function(e) {
|
||||
if (e.state && e.state.drillDown !== undefined) {
|
||||
const stateIndex = e.state.drillDown;
|
||||
if (stateIndex >= 0 && stateIndex < drillDownHistory.length) {
|
||||
historyIndex = stateIndex;
|
||||
navigateToHistoryState(drillDownHistory[historyIndex]);
|
||||
}
|
||||
} else {
|
||||
// No state or initial state - go to root
|
||||
if (drillDownHistory.length > 0) {
|
||||
historyIndex = 0;
|
||||
navigateToHistoryState(drillDownHistory[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Function to parse CSV data
|
||||
async function parseCSV(file) {
|
||||
const response = await fetch(file);
|
||||
@ -323,6 +399,10 @@ function renderChart(data) {
|
||||
// Store the original data for resetting (module-level variable)
|
||||
originalSunburstData = JSON.parse(JSON.stringify(sunburstData));
|
||||
|
||||
// Reset and initialize history with the root state
|
||||
resetHistory();
|
||||
saveToHistory(sunburstData.data, sunburstData.total, null, true);
|
||||
|
||||
// Get the currently selected month
|
||||
const selectedMonth = document.getElementById('month-select').value;
|
||||
const russianMonth = getRussianMonthName(selectedMonth);
|
||||
@ -775,6 +855,9 @@ function renderChart(data) {
|
||||
});
|
||||
}
|
||||
|
||||
// Save new state to history
|
||||
saveToHistory(newData, params.value, params.name);
|
||||
|
||||
// Update the chart with the new data structure
|
||||
option.series.data = newData;
|
||||
|
||||
@ -791,7 +874,7 @@ function renderChart(data) {
|
||||
|
||||
myChart.setOption(option);
|
||||
|
||||
// Add click handler for the center to reset view
|
||||
// Add click handler for the center to go back in history
|
||||
const zr = myChart.getZr();
|
||||
zr.on('click', function(params) {
|
||||
const x = params.offsetX;
|
||||
@ -806,14 +889,8 @@ function renderChart(data) {
|
||||
|
||||
// Check if click is within the center circle
|
||||
const distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
|
||||
if (distance < innerRadius) {
|
||||
// Reset to original view - use module-level originalSunburstData
|
||||
const currentMonth = document.getElementById('month-select').value;
|
||||
const currentRussianMonth = getRussianMonthName(currentMonth);
|
||||
option.series.data = originalSunburstData.data;
|
||||
option.graphic.elements[0].style.text = currentRussianMonth + '\n' + originalSunburstData.total.toFixed(0).toLocaleString() + ' ₽';
|
||||
myChart.setOption(option, { replaceMerge: ['series'] });
|
||||
setupHoverEvents(originalSunburstData);
|
||||
if (distance < innerRadius && historyIndex > 0) {
|
||||
history.back(); // Use browser history - triggers popstate
|
||||
}
|
||||
});
|
||||
|
||||
@ -1054,6 +1131,10 @@ async function selectMonth(index) {
|
||||
// Update the module-level original data for center-click reset
|
||||
originalSunburstData = JSON.parse(JSON.stringify(sunburstData));
|
||||
|
||||
// Reset and initialize history for the new month
|
||||
resetHistory();
|
||||
saveToHistory(sunburstData.data, sunburstData.total, null, true);
|
||||
|
||||
// Update only the series data and preserve layout
|
||||
const oldData = option.series.data;
|
||||
const newData = sunburstData.data;
|
||||
@ -1723,10 +1804,10 @@ function setupHoverEvents(sunburstData, contextName = null) {
|
||||
myChart.on('mouseout', function(params) {
|
||||
if (params.data) {
|
||||
isOverChartSector = false;
|
||||
isInsideSection = false;
|
||||
// Hide the floating eye button (unless hovering over it)
|
||||
hideChartEyeButton();
|
||||
// Reset details with a small delay to allow eye button mouseenter to fire first
|
||||
// But only if we're not still inside another section
|
||||
// Reset details with a small delay to allow mouseover of next sector to fire first
|
||||
setTimeout(() => {
|
||||
if (!isOverEyeButton && !isInsideSection) {
|
||||
showDefaultView();
|
||||
@ -1735,6 +1816,12 @@ function setupHoverEvents(sunburstData, contextName = null) {
|
||||
}
|
||||
});
|
||||
|
||||
// Also reset when mouse leaves the chart container entirely
|
||||
chartDom.addEventListener('mouseleave', function() {
|
||||
isInsideSection = false;
|
||||
showDefaultView();
|
||||
});
|
||||
|
||||
// Add back the downplay event handler - this is triggered when sections lose emphasis
|
||||
myChart.on('downplay', function(params) {
|
||||
// Reset to default view when a section is no longer emphasized (unless hovering eye button or still in section)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user