Improve responsive chart scaling and hide center label earlier

- Scale chart layers on mobile (≤500px) to fill container while
  keeping same hole size as desktop (20%)
- Add resize handler to dynamically update proportions when crossing
  500px threshold
- Hide center label at 850px (stacked layout) instead of 500px to
  prevent overlap with chart
- Center chart at 50% on mobile since details box is below

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Anton Volnuhin 2026-02-01 15:57:16 +03:00
parent 2b45a42b49
commit d76e80dc02
2 changed files with 84 additions and 28 deletions

88
app.js
View File

@ -470,11 +470,25 @@ function renderChart(data) {
// Gradual transition between 640-1000px // Gradual transition between 640-1000px
const transitionProgress = (screenWidth - 640) / 360; // 0 to 1 const transitionProgress = (screenWidth - 640) / 360; // 0 to 1
centerPosition = 40 + (transitionProgress * 10); // 40 to 50 centerPosition = 40 + (transitionProgress * 10); // 40 to 50
} else if (screenWidth <= 500) {
// Mobile: center the chart since details box is below
centerPosition = 50;
} else { } else {
// For smaller screens // For smaller screens (500-640px)
centerPosition = 40; centerPosition = 40;
} }
// Mobile: scale chart to fill container, hide outside labels
const isMobile = screenWidth <= 500;
// Mobile: extend layers to fill container, keeping same hole size as desktop
// Hole stays at 20%, layers scaled to fill remaining 80% (vs 55% on desktop)
const level1Inner = '20%';
const level1Outer = isMobile ? '56%' : '45%';
const level2Outer = isMobile ? '93%' : '70%';
const level3Outer = isMobile ? '100%' : '75%';
const outerRadius = isMobile ? '100%' : '95%';
option = { option = {
backgroundColor: '#fff', backgroundColor: '#fff',
grid: { grid: {
@ -489,7 +503,7 @@ function renderChart(data) {
//animationEasingUpdate: 'cubicInOut', //animationEasingUpdate: 'cubicInOut',
series: { series: {
type: 'sunburst', type: 'sunburst',
radius: [0, '95%'], radius: [0, outerRadius],
center: [`${centerPosition}%`, '50%'], center: [`${centerPosition}%`, '50%'],
startAngle: 0, startAngle: 0,
nodeClick: false, nodeClick: false,
@ -518,8 +532,8 @@ function renderChart(data) {
{}, {},
{ {
// First level - Categories // First level - Categories
r0: '20%', r0: level1Inner,
r: '45%', r: level1Outer,
label: { label: {
show: true, show: true,
rotate: 'radial', rotate: 'radial',
@ -538,8 +552,8 @@ function renderChart(data) {
}, },
{ {
// Second level - Subcategories // Second level - Subcategories
r0: '45%', r0: level1Outer,
r: '70%', r: level2Outer,
label: { label: {
show: function(param) { show: function(param) {
// Show labels for sectors that are at least 5% of the total // Show labels for sectors that are at least 5% of the total
@ -617,16 +631,17 @@ function renderChart(data) {
} }
}, },
{ {
// Third level - Microcategories - a bit wider than before // Third level - Microcategories
r0: '70%', r0: level2Outer,
r: '75%', r: level3Outer,
label: { label: {
// Only show labels conditionally based on segment size // Only show labels conditionally based on segment size
show: function(param) { // On mobile, hide outside labels to maximize chart size
show: isMobile ? false : function(param) {
// Show label if segment is wide enough (>1%) // Show label if segment is wide enough (>1%)
return param.percent > 0.000; return param.percent > 0.000;
}, },
position: 'outside', position: isMobile ? 'inside' : 'outside',
padding: 3, padding: 3,
minAngle: 3, // Add this - default is 5, reducing it will show more labels minAngle: 3, // Add this - default is 5, reducing it will show more labels
silent: false, silent: false,
@ -1756,16 +1771,51 @@ function adjustChartSize() {
if (!option) return; if (!option) return;
const screenWidth = window.innerWidth; const screenWidth = window.innerWidth;
const detailsBox = document.getElementById('details-box'); const isMobile = screenWidth <= 500;
const detailsWidth = detailsBox.offsetWidth;
// Calculate center position with a smooth transition // Mobile: extend layers to fill container, keeping same hole size as desktop
// Hole stays at 20%, layers scaled to fill remaining 80% (vs 55% on desktop)
const level1Inner = '20%';
const level1Outer = isMobile ? '56%' : '45%';
const level2Outer = isMobile ? '93%' : '70%';
const level3Outer = isMobile ? '100%' : '75%';
const outerRadius = isMobile ? '100%' : '95%';
// Update layer proportions
option.series.levels[1].r0 = level1Inner;
option.series.levels[1].r = level1Outer;
option.series.levels[2].r0 = level1Outer;
option.series.levels[2].r = level2Outer;
option.series.levels[3].r0 = level2Outer;
option.series.levels[3].r = level3Outer;
option.series.radius = [0, outerRadius];
// Update level 3 labels: hide on mobile, show on desktop (with conditions)
if (isMobile) {
option.series.levels[3].label.show = false;
option.series.levels[3].label.position = 'inside';
} else if (screenWidth < 950) {
option.series.levels[3].label.show = false;
option.series.levels[3].label.position = 'outside';
} else {
option.series.levels[3].label.show = function(param) {
return param.percent > 0.000;
};
option.series.levels[3].label.position = 'outside';
}
// Calculate center position
let centerPosition; let centerPosition;
if (screenWidth >= 1000) {
if (screenWidth < 950) centerPosition = 50;
option.series.levels[3].label.show=false; } else if (screenWidth >= 640) {
else option.series.levels[3].label.show=true; const transitionProgress = (screenWidth - 640) / 360;
centerPosition = 50; centerPosition = 40 + (transitionProgress * 10);
} else if (isMobile) {
centerPosition = 50;
} else {
centerPosition = 40;
}
// Update chart center position // Update chart center position
option.series.center = [`${centerPosition}%`, '50%']; option.series.center = [`${centerPosition}%`, '50%'];

View File

@ -359,6 +359,10 @@ body {
.top-item-amount { .top-item-amount {
min-width: 80px; min-width: 80px;
} }
.center-label {
display: none;
}
} }
@media (max-width: 500px) { @media (max-width: 500px) {
@ -394,10 +398,6 @@ body {
gap: 4px; gap: 4px;
} }
.center-label {
display: none;
}
.container { .container {
padding: 15px 10px; padding: 15px 10px;
} }
@ -405,6 +405,12 @@ body {
.chart-wrapper { .chart-wrapper {
height: 95vw; height: 95vw;
} }
#chart-container {
background-color: transparent;
box-shadow: none;
border-radius: 0;
}
} }
@media (max-width: 400px) { @media (max-width: 400px) {
@ -432,7 +438,7 @@ body {
} }
.chart-wrapper { .chart-wrapper {
height: 98vw; height: 100vw;
} }
.details-header { .details-header {