diff --git a/app.js b/app.js index 9318d89..d2119fe 100644 --- a/app.js +++ b/app.js @@ -119,6 +119,9 @@ function transformToSunburst(data) { } }); + // Sort categories by value (largest to smallest) + categories.sort((a, b) => b.value - a.value); + // Convert the map to an array structure for ECharts const result = []; @@ -134,11 +137,14 @@ function transformToSunburst(data) { } }; + // Get subcategories and sort by value const subcategories = []; for (const subcatKey in category.children) { subcategories.push(category.children[subcatKey]); } + subcategories.sort((a, b) => b.value - a.value); + // Process each subcategory subcategories.forEach(subcategory => { const subcategoryNode = { name: subcategory.name, @@ -149,11 +155,14 @@ function transformToSunburst(data) { } }; + // Get microcategories and sort by value const microcategories = []; for (const microKey in subcategory.children) { microcategories.push(subcategory.children[microKey]); } + microcategories.sort((a, b) => b.value - a.value); + // Add microcategories to subcategory microcategories.forEach(micro => { subcategoryNode.children.push({ name: micro.name, @@ -202,14 +211,12 @@ function renderChart(data) { center: ['40%', '50%'], // Move chart to the left nodeClick: 'rootToNode', // To enable drill down on click data: sunburstData.data, + sort: null, // Use 'null' to maintain the sorting we did in the data transformation label: { show: true, formatter: function(param) { if (param.depth === 0) { - // Add line breaks for long category names - if (param.name.length > 10) { - return param.name.replace(/(.{1,10})(?: |$)/g, "$1\n").trim(); - } + // No word wrapping for top-level categories return param.name; } else { return ''; @@ -237,11 +244,14 @@ function renderChart(data) { lineHeight: 15, verticalAlign: 'center', position: 'inside', + formatter: function(param) { + // No special formatting for level 1 + return param.name; + } }, itemStyle: { borderWidth: 2 } - }, { // Second level - Subcategories @@ -253,7 +263,61 @@ function renderChart(data) { align: 'left', position: 'inside', distance: 5, - + formatter: function(param) { + // If there's only one word, never wrap it + if (!param.name.includes(' ')) { + return param.name; + } + + // If the text contains spaces, consider word wrapping for better visibility + const words = param.name.split(' '); + + // Skip wrapping for single words or very small sectors + // Estimate sector size from value percentage + if (words.length === 1 || param.percent < 0.03) { + return param.name; + } + + // Process words to keep short prepositions (< 4 chars) with the next word + const processedWords = []; + let i = 0; + while (i < words.length) { + if (i < words.length - 1 && words[i].length < 4) { + // Combine short word with the next word + processedWords.push(words[i] + ' ' + words[i+1]); + i += 2; + } else { + processedWords.push(words[i]); + i++; + } + } + + // Skip wrapping if we're down to just one processed word + if (processedWords.length === 1) { + return processedWords[0]; + } + + // If only 2 processed words, put one on each line + if (processedWords.length == 2) { + return processedWords[0] + '\n' + processedWords[1]; + } + // If 3 processed words, put each word on its own line + else if (processedWords.length == 3) { + return processedWords[0] + '\n' + processedWords[1] + '\n' + processedWords[2]; + } + // For more words, split more aggressively + else if (processedWords.length > 3) { + // Try to create 3 relatively even lines + const part1 = Math.floor(processedWords.length / 3); + const part2 = Math.floor(processedWords.length * 2 / 3); + + return processedWords.slice(0, part1).join(' ') + '\n' + + processedWords.slice(part1, part2).join(' ') + '\n' + + processedWords.slice(part2).join(' '); + } + + return param.name; + } }, itemStyle: { borderWidth: 1 @@ -276,9 +340,58 @@ function renderChart(data) { silent: false, fontSize: 10, formatter: function(param) { - if (param.name.length > 10) { - return param.name.slice(0, 8) + '...'; + // If there's only one word, never wrap it + if (!param.name.includes(' ')) { + return param.name; } + + // If the text contains spaces, consider word wrapping for better visibility + const words = param.name.split(' '); + + // Skip wrapping for single words or very small sectors + // Estimate sector size from value percentage + if (words.length === 1 || param.percent < 0.02) { + return param.name; + } + + // Process words to keep short prepositions (< 4 chars) with the next word + const processedWords = []; + let i = 0; + while (i < words.length) { + if (i < words.length - 1 && words[i].length < 4) { + // Combine short word with the next word + processedWords.push(words[i] + ' ' + words[i+1]); + i += 2; + } else { + processedWords.push(words[i]); + i++; + } + } + + // Skip wrapping if we're down to just one processed word + if (processedWords.length === 1) { + return processedWords[0]; + } + + // If only 2 processed words, put one on each line + if (processedWords.length == 2) { + return processedWords[0] + '\n' + processedWords[1]; + } + // If 3 processed words, put each word on its own line + else if (processedWords.length == 3) { + return processedWords[0] + '\n' + processedWords[1] + '\n' + processedWords[2]; + } + // For more words, split more aggressively + else if (processedWords.length > 3) { + // Try to create 3 relatively even lines + const part1 = Math.floor(processedWords.length / 3); + const part2 = Math.floor(processedWords.length * 2 / 3); + + return processedWords.slice(0, part1).join(' ') + '\n' + + processedWords.slice(part1, part2).join(' ') + '\n' + + processedWords.slice(part2).join(' '); + } + return param.name; } }, @@ -292,8 +405,6 @@ function renderChart(data) { }, // Add more space between wedges gap: 2, - // Allow the chart to sort segments by value - sort: null, tooltip: { trigger: 'item', formatter: function(info) { @@ -365,6 +476,9 @@ function renderChart(data) { } if (targetData && targetData.children && targetData.children.length > 0) { + // Sort children by value before recoloring + targetData.children.sort((a, b) => b.value - a.value); + // Recolor children with unique colors targetData.children.forEach((child, i) => { const color = colorPalette[i % colorPalette.length]; @@ -374,6 +488,9 @@ function renderChart(data) { // If the child has children (microcategories), color them too if (child.children && child.children.length > 0) { + // Sort microcategories by value + child.children.sort((a, b) => b.value - a.value); + const microColors = generateColorGradient(color, child.children.length); child.children.forEach((micro, j) => { micro.itemStyle = {