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) {
// Case 1: Node has children (category or subcategory)
const sortedChildren = sortBySubcategoryOrder(params.data.children, params.name);
// Process each child
const predefinedOrder = subcategoryOrder[params.name] || [];
// Process each child, tracking unknown item count for color assignment
let unknownCount = 0;
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 = {
name: child.name,
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
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]) {
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
@ -1273,14 +1281,20 @@ function generateDrilledDownGradient(monthData, path) {
const gradientStops = [];
let currentAngle = 0;
const predefinedOrder = parentCategory ? (subcategoryOrder[parentCategory] || []) : [];
let unknownCount = 0;
sortedKeys.forEach((key, index) => {
const percentage = totals[key] / total;
const angle = percentage * 360;
// Use fixed subcategory colors for first level, fallback to palette for deeper levels
const color = (level === 'subcategory' && parentCategory)
? getSubcategoryColor(parentCategory, key, index)
: defaultColorPalette[index % defaultColorPalette.length];
let color;
if (level === 'subcategory' && parentCategory) {
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`);
currentAngle += angle;
@ -1395,9 +1409,12 @@ async function loadAvailableMonths() {
function transformDrillDownData(parentNode, parentCategoryName) {
const newData = [];
const sortedChildren = sortBySubcategoryOrder(parentNode.children, parentCategoryName);
const predefinedOrder = subcategoryOrder[parentCategoryName] || [];
let unknownCount = 0;
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 = {
name: child.name,
value: child.value,
@ -1618,20 +1635,26 @@ async function selectMonth(index) {
if (navigatedState) {
// 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;
targetTotal = navigatedState.total;
targetName = navigatedState.contextName;
targetPath = navigatedState.path;
// Save the drilled-down state
saveToHistory(targetData, targetTotal, targetName, false, targetPath);
} else {
// Stay at root level
targetData = sunburstData.data;
targetTotal = sunburstData.total;
targetName = null;
targetPath = [];
}
// Save initial state with the path
saveToHistory(targetData, targetTotal, targetName, true, targetPath);
// Save initial state
saveToHistory(targetData, targetTotal, targetName, true, targetPath);
}
// Update the data
option.series.data = targetData;