Improve center label and details box styling

- Move center label from ECharts canvas to HTML overlay for better font rendering
- Style ₽ symbol separately with lighter color (#888) and slightly smaller size (20px)
- Use tighter line height (1.0) for center label
- Remove "Детали" heading from details box, use total as header
- Remove top border from details header
- Increase header font sizes to 20px
- Add more whitespace above "Top Items:"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Anton Volnuhin 2026-02-01 15:02:31 +03:00
parent fbbf94c3c4
commit c441d5979f
3 changed files with 77 additions and 42 deletions

61
app.js
View File

@ -1,10 +1,21 @@
// Format RUB amount: no decimals, thin space (U+2009) as thousands separator // Format RUB amount: no decimals, narrow no-break space as thousands separator
function formatRUB(amount) { function formatRUB(amount) {
return Math.round(amount) return Math.round(amount)
.toLocaleString('ru-RU') .toLocaleString('ru-RU')
.replace(/\u00a0/g, '\u202F'); .replace(/\u00a0/g, '\u202F');
} }
// Update HTML center label (month in gray, category optional, amount in black)
function updateCenterLabel(month, amount, category = null) {
const labelEl = document.getElementById('center-label');
if (!labelEl) return;
labelEl.querySelector('.center-month').textContent = month;
labelEl.querySelector('.center-category').textContent = category || '';
labelEl.querySelector('.center-amount-num').textContent = formatRUB(amount);
labelEl.querySelector('.center-rub').textContent = '\u202F₽';
}
// Initialize the chart // Initialize the chart
const chartDom = document.getElementById('chart-container'); const chartDom = document.getElementById('chart-container');
const myChart = echarts.init(chartDom); const myChart = echarts.init(chartDom);
@ -50,13 +61,8 @@ function navigateToHistoryState(state) {
const russianMonth = getRussianMonthName(document.getElementById('month-select').value); const russianMonth = getRussianMonthName(document.getElementById('month-select').value);
option.series.data = state.data; option.series.data = state.data;
if (state.contextName) {
option.graphic.elements[0].style.text = `${russianMonth}\n${state.contextName}\n${formatRUB(state.total)}\u202F₽`;
} else {
option.graphic.elements[0].style.text = russianMonth + '\n' + formatRUB(state.total) + '\u202F₽';
}
myChart.setOption(option, { replaceMerge: ['series'] }); myChart.setOption(option, { replaceMerge: ['series'] });
updateCenterLabel(russianMonth, state.total, state.contextName);
setupHoverEvents({ total: state.total, data: state.data }, state.contextName); setupHoverEvents({ total: state.total, data: state.data }, state.contextName);
// Restore drill path and update mini-charts // Restore drill path and update mini-charts
@ -705,22 +711,7 @@ function renderChart(data) {
} }
}, },
graphic: { graphic: {
elements: [ elements: [] // Center label is now HTML overlay
{
type: 'text',
left: 'center',
top: 'middle',
z: 100,
style: {
text: russianMonth + '\n' + formatRUB(sunburstData.total) + '\u202F₽',
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
fontWeight: 'bold',
fontSize: 18,
textAlign: 'left',
fill: '#000'
}
}
]
} }
}; };
@ -914,9 +905,8 @@ function renderChart(data) {
// Update the center text to show the drilled-down category // Update the center text to show the drilled-down category
const russianMonth = getRussianMonthName(document.getElementById('month-select').value); const russianMonth = getRussianMonthName(document.getElementById('month-select').value);
option.graphic.elements[0].style.text = `${russianMonth}\n${params.name}\n${formatRUB(params.value)}\u202F₽`;
myChart.setOption(option, { replaceMerge: ['series'] }); myChart.setOption(option, { replaceMerge: ['series'] });
updateCenterLabel(russianMonth, params.value, params.name);
// Update hover events with the new data structure, passing the drilled-down name // Update hover events with the new data structure, passing the drilled-down name
setupHoverEvents({ total: params.value, data: newData }, params.name); setupHoverEvents({ total: params.value, data: newData }, params.name);
@ -927,7 +917,10 @@ function renderChart(data) {
}); });
myChart.setOption(option); myChart.setOption(option);
// Update HTML center label
updateCenterLabel(russianMonth, sunburstData.total);
// Add click handler for the center to go back in history // Add click handler for the center to go back in history
const zr = myChart.getZr(); const zr = myChart.getZr();
zr.on('click', function(params) { zr.on('click', function(params) {
@ -1685,11 +1678,6 @@ async function selectMonth(index) {
// Update the total amount in the center text // Update the total amount in the center text
const russianMonth = getRussianMonthName(month); const russianMonth = getRussianMonthName(month);
if (targetName) {
option.graphic.elements[0].style.text = `${russianMonth}\n${targetName}\n${formatRUB(targetTotal)}\u202F₽`;
} else {
option.graphic.elements[0].style.text = russianMonth + '\n' + formatRUB(targetTotal) + '\u202F₽';
}
myChart.setOption({ myChart.setOption({
series: [{ series: [{
@ -1698,13 +1686,14 @@ async function selectMonth(index) {
layoutAnimation: true, layoutAnimation: true,
animationDuration: 500, animationDuration: 500,
animationEasing: 'cubicInOut' animationEasing: 'cubicInOut'
}], }]
graphic: option.graphic
}, { }, {
lazyUpdate: false, lazyUpdate: false,
silent: false silent: false
}); });
updateCenterLabel(russianMonth, targetTotal, targetName);
// Update hover events // Update hover events
setupHoverEvents({ total: targetTotal, data: targetData }, targetName); setupHoverEvents({ total: targetTotal, data: targetData }, targetName);
@ -1745,7 +1734,7 @@ function updateMonthNavigator() {
// Initialize the visualization // Initialize the visualization
async function initVisualization() { async function initVisualization() {
await loadAvailableMonths(); await loadAvailableMonths();
// Ensure the chart is properly sized on initial load // Ensure the chart is properly sized on initial load
myChart.resize(); myChart.resize();
} }
@ -1780,9 +1769,7 @@ function adjustChartSize() {
// Update chart center position // Update chart center position
option.series.center = [`${centerPosition}%`, '50%']; option.series.center = [`${centerPosition}%`, '50%'];
option.graphic.elements[0].left = 'center';
option.graphic.elements[0].top = 'middle';
myChart.setOption(option); myChart.setOption(option);
} }

View File

@ -21,6 +21,11 @@
</div> </div>
<div class="content-wrapper"> <div class="content-wrapper">
<div id="chart-container"></div> <div id="chart-container"></div>
<div id="center-label" class="center-label">
<div class="center-month"></div>
<div class="center-category"></div>
<div class="center-amount"><span class="center-amount-num"></span><span class="center-rub"></span></div>
</div>
<button id="chart-eye-btn" class="chart-eye-btn" title="View transactions"> <button id="chart-eye-btn" class="chart-eye-btn" title="View transactions">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/> <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
@ -28,7 +33,6 @@
</svg> </svg>
</button> </button>
<div id="details-box" class="details-box"> <div id="details-box" class="details-box">
<h3>Детали</h3>
<div id="details-header" class="details-header"> <div id="details-header" class="details-header">
<span class="hover-name">Hover over a segment to see details</span> <span class="hover-name">Hover over a segment to see details</span>
<span class="hover-amount"></span> <span class="hover-amount"></span>

View File

@ -140,6 +140,51 @@ body {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
} }
/* Center label overlay */
.center-label {
position: absolute;
left: 35%; /* Half of chart-container width (70% / 2) */
top: 50%;
transform: translate(-50%, -50%);
text-align: left;
pointer-events: none;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", system-ui, sans-serif;
z-index: 10;
}
.center-month {
font-size: 18px;
font-weight: 400;
color: #666;
line-height: 1.0;
}
.center-category {
font-size: 18px;
font-weight: 600;
color: #333;
line-height: 1.0;
}
.center-category:empty {
display: none;
}
.center-amount {
font-size: 22px;
font-weight: 700;
line-height: 1.0;
}
.center-amount-num {
color: #000;
}
.center-rub {
color: #888;
font-size: 20px;
}
/* Override ECharts default pointer cursor - only eye button should have pointer */ /* Override ECharts default pointer cursor - only eye button should have pointer */
#chart-container canvas { #chart-container canvas {
cursor: default !important; cursor: default !important;
@ -174,7 +219,7 @@ body {
} }
#details-box h4 { #details-box h4 {
margin: 10px 0 5px; margin: 18px 0 5px;
color: #555; color: #555;
font-size: 14px; font-size: 14px;
} }
@ -186,7 +231,6 @@ body {
margin-bottom: 20px; margin-bottom: 20px;
padding: 10px 0; padding: 10px 0;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
border-top: 1px solid #eee;
} }
.hover-name { .hover-name {
@ -196,11 +240,11 @@ body {
flex: 1; flex: 1;
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 16px; font-size: 20px;
} }
.hover-amount { .hover-amount {
font-size: 18px; font-size: 20px;
color: #0066cc; color: #0066cc;
font-weight: bold; font-weight: bold;
margin-left: 10px; margin-left: 10px;