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:
parent
d934b5465b
commit
d93c7c6051
45
app.js
45
app.js
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user