Fix drill-down navigation and unknown subcategory colors

Bug A: Center click to go back now works after switching months while
drilled down. Fixed by saving root state BEFORE drilled state in
selectMonth, so historyIndex=0 always points to root.

Bug B: Unknown subcategories (not in December data) now get distinct
colors. Fixed by tracking unknownCount separately and using
(predefinedCount + unknownIndex) % 10 to assign colors starting after
the predefined palette positions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Anton Volnuhin 2026-01-31 18:47:45 +03:00
parent d934b5465b
commit d93c7c6051

47
app.js
View File

@ -726,10 +726,13 @@ function renderChart(data) {
if (params.data.children && params.data.children.length > 0) { if (params.data.children && params.data.children.length > 0) {
// Case 1: Node has children (category or subcategory) // Case 1: Node has children (category or subcategory)
const sortedChildren = sortBySubcategoryOrder(params.data.children, params.name); const sortedChildren = sortBySubcategoryOrder(params.data.children, params.name);
const predefinedOrder = subcategoryOrder[params.name] || [];
// Process each child
// Process each child, tracking unknown item count for color assignment
let unknownCount = 0;
sortedChildren.forEach((child, i) => { sortedChildren.forEach((child, i) => {
const color = getSubcategoryColor(params.name, child.name, i); const isUnknown = !predefinedOrder.includes(child.name);
const color = getSubcategoryColor(params.name, child.name, i, isUnknown ? unknownCount++ : null);
const newCategory = { const newCategory = {
name: child.name, name: child.name,
value: child.value, value: child.value,
@ -1078,11 +1081,16 @@ for (const category in subcategoryColors) {
} }
// Get color for a subcategory, using fixed mapping or fallback to index-based // Get color for a subcategory, using fixed mapping or fallback to index-based
function getSubcategoryColor(category, subcategory, index) { // unknownIndex: for unknown subcategories, pass the count of unknown items before this one
function getSubcategoryColor(category, subcategory, index, unknownIndex = null) {
if (subcategoryColors[category] && subcategoryColors[category][subcategory]) { if (subcategoryColors[category] && subcategoryColors[category][subcategory]) {
return subcategoryColors[category][subcategory]; return subcategoryColors[category][subcategory];
} }
return defaultColorPalette[index % defaultColorPalette.length]; // For unknown subcategories, use colors starting after the predefined ones
const predefinedCount = subcategoryOrder[category] ? subcategoryOrder[category].length : 0;
// Use unknownIndex if provided, otherwise calculate from index minus predefined items that might be before
const colorIndex = unknownIndex !== null ? unknownIndex : index;
return defaultColorPalette[(predefinedCount + colorIndex) % defaultColorPalette.length];
} }
// Sort items by fixed subcategory order, with unknown items at the end sorted by value // Sort items by fixed subcategory order, with unknown items at the end sorted by value
@ -1273,14 +1281,20 @@ function generateDrilledDownGradient(monthData, path) {
const gradientStops = []; const gradientStops = [];
let currentAngle = 0; let currentAngle = 0;
const predefinedOrder = parentCategory ? (subcategoryOrder[parentCategory] || []) : [];
let unknownCount = 0;
sortedKeys.forEach((key, index) => { sortedKeys.forEach((key, index) => {
const percentage = totals[key] / total; const percentage = totals[key] / total;
const angle = percentage * 360; const angle = percentage * 360;
// Use fixed subcategory colors for first level, fallback to palette for deeper levels // Use fixed subcategory colors for first level, fallback to palette for deeper levels
const color = (level === 'subcategory' && parentCategory) let color;
? getSubcategoryColor(parentCategory, key, index) if (level === 'subcategory' && parentCategory) {
: defaultColorPalette[index % defaultColorPalette.length]; const isUnknown = !predefinedOrder.includes(key);
color = getSubcategoryColor(parentCategory, key, index, isUnknown ? unknownCount++ : null);
} else {
color = defaultColorPalette[index % defaultColorPalette.length];
}
gradientStops.push(`${color} ${currentAngle}deg ${currentAngle + angle}deg`); gradientStops.push(`${color} ${currentAngle}deg ${currentAngle + angle}deg`);
currentAngle += angle; currentAngle += angle;
@ -1395,9 +1409,12 @@ async function loadAvailableMonths() {
function transformDrillDownData(parentNode, parentCategoryName) { function transformDrillDownData(parentNode, parentCategoryName) {
const newData = []; const newData = [];
const sortedChildren = sortBySubcategoryOrder(parentNode.children, parentCategoryName); const sortedChildren = sortBySubcategoryOrder(parentNode.children, parentCategoryName);
const predefinedOrder = subcategoryOrder[parentCategoryName] || [];
let unknownCount = 0;
sortedChildren.forEach((child, i) => { sortedChildren.forEach((child, i) => {
const color = getSubcategoryColor(parentCategoryName, child.name, i); const isUnknown = !predefinedOrder.includes(child.name);
const color = getSubcategoryColor(parentCategoryName, child.name, i, isUnknown ? unknownCount++ : null);
const newCategory = { const newCategory = {
name: child.name, name: child.name,
value: child.value, value: child.value,
@ -1618,20 +1635,26 @@ async function selectMonth(index) {
if (navigatedState) { if (navigatedState) {
// Successfully navigated to (part of) the path // Successfully navigated to (part of) the path
// First save the root state so we can go back to it
saveToHistory(sunburstData.data, sunburstData.total, null, true, []);
targetData = navigatedState.data; targetData = navigatedState.data;
targetTotal = navigatedState.total; targetTotal = navigatedState.total;
targetName = navigatedState.contextName; targetName = navigatedState.contextName;
targetPath = navigatedState.path; targetPath = navigatedState.path;
// Save the drilled-down state
saveToHistory(targetData, targetTotal, targetName, false, targetPath);
} else { } else {
// Stay at root level // Stay at root level
targetData = sunburstData.data; targetData = sunburstData.data;
targetTotal = sunburstData.total; targetTotal = sunburstData.total;
targetName = null; targetName = null;
targetPath = []; targetPath = [];
}
// Save initial state with the path // Save initial state
saveToHistory(targetData, targetTotal, targetName, true, targetPath); saveToHistory(targetData, targetTotal, targetName, true, targetPath);
}
// Update the data // Update the data
option.series.data = targetData; option.series.data = targetData;