Use consistent RUB formatting: integers with narrow no-break space
Add formatRUB() helper that formats amounts without decimals and uses U+202F (narrow no-break space) as thousands separator and before ₽ symbol. This prevents unwanted line breaks within currency amounts. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3e0726d34f
commit
fbbf94c3c4
35
app.js
35
app.js
@ -1,3 +1,10 @@
|
|||||||
|
// Format RUB amount: no decimals, thin space (U+2009) as thousands separator
|
||||||
|
function formatRUB(amount) {
|
||||||
|
return Math.round(amount)
|
||||||
|
.toLocaleString('ru-RU')
|
||||||
|
.replace(/\u00a0/g, '\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);
|
||||||
@ -44,9 +51,9 @@ function navigateToHistoryState(state) {
|
|||||||
option.series.data = state.data;
|
option.series.data = state.data;
|
||||||
|
|
||||||
if (state.contextName) {
|
if (state.contextName) {
|
||||||
option.graphic.elements[0].style.text = `${russianMonth}\n${state.contextName}\n${state.total.toFixed(0).toLocaleString()} ₽`;
|
option.graphic.elements[0].style.text = `${russianMonth}\n${state.contextName}\n${formatRUB(state.total)}\u202F₽`;
|
||||||
} else {
|
} else {
|
||||||
option.graphic.elements[0].style.text = russianMonth + '\n' + state.total.toFixed(0).toLocaleString() + ' ₽';
|
option.graphic.elements[0].style.text = russianMonth + '\n' + formatRUB(state.total) + '\u202F₽';
|
||||||
}
|
}
|
||||||
|
|
||||||
myChart.setOption(option, { replaceMerge: ['series'] });
|
myChart.setOption(option, { replaceMerge: ['series'] });
|
||||||
@ -687,13 +694,13 @@ function renderChart(data) {
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
formatter: function(info) {
|
formatter: function(info) {
|
||||||
const value = info.value.toLocaleString();
|
const value = formatRUB(info.value);
|
||||||
const name = info.name;
|
const name = info.name;
|
||||||
|
|
||||||
// Calculate percentage of total
|
// Calculate percentage of total
|
||||||
const percentage = ((info.value / sunburstData.total) * 100).toFixed(1);
|
const percentage = ((info.value / sunburstData.total) * 100).toFixed(1);
|
||||||
|
|
||||||
return `${name}<br/>Amount: ${value} RUB<br/>Percentage: ${percentage}%`;
|
return `${name}<br/>Amount: ${value}\u202F₽<br/>Percentage: ${percentage}%`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -705,7 +712,7 @@ function renderChart(data) {
|
|||||||
top: 'middle',
|
top: 'middle',
|
||||||
z: 100,
|
z: 100,
|
||||||
style: {
|
style: {
|
||||||
text: russianMonth + '\n' + sunburstData.total.toFixed(0).toLocaleString() + ' ₽',
|
text: russianMonth + '\n' + formatRUB(sunburstData.total) + '\u202F₽',
|
||||||
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@ -907,7 +914,7 @@ 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${params.value.toFixed(0).toLocaleString()} ₽`;
|
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'] });
|
||||||
|
|
||||||
@ -1679,9 +1686,9 @@ 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) {
|
if (targetName) {
|
||||||
option.graphic.elements[0].style.text = `${russianMonth}\n${targetName}\n${targetTotal.toFixed(0).toLocaleString()} ₽`;
|
option.graphic.elements[0].style.text = `${russianMonth}\n${targetName}\n${formatRUB(targetTotal)}\u202F₽`;
|
||||||
} else {
|
} else {
|
||||||
option.graphic.elements[0].style.text = russianMonth + '\n' + targetTotal.toFixed(0).toLocaleString() + ' ₽';
|
option.graphic.elements[0].style.text = russianMonth + '\n' + formatRUB(targetTotal) + '\u202F₽';
|
||||||
}
|
}
|
||||||
|
|
||||||
myChart.setOption({
|
myChart.setOption({
|
||||||
@ -2098,7 +2105,7 @@ function setupHoverEvents(sunburstData, contextName = null) {
|
|||||||
|
|
||||||
const amountSpan = document.createElement('span');
|
const amountSpan = document.createElement('span');
|
||||||
amountSpan.className = 'top-item-amount';
|
amountSpan.className = 'top-item-amount';
|
||||||
amountSpan.textContent = item.value.toLocaleString() + ' ₽';
|
amountSpan.textContent = formatRUB(item.value) + '\u202F₽';
|
||||||
itemDiv.appendChild(amountSpan);
|
itemDiv.appendChild(amountSpan);
|
||||||
topItemsElement.appendChild(itemDiv);
|
topItemsElement.appendChild(itemDiv);
|
||||||
});
|
});
|
||||||
@ -2125,7 +2132,7 @@ function setupHoverEvents(sunburstData, contextName = null) {
|
|||||||
<circle cx="12" cy="12" r="3"/>
|
<circle cx="12" cy="12" r="3"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<span class="hover-amount">${value.toLocaleString()} ₽</span>
|
<span class="hover-amount">${formatRUB(value)}\u202F₽</span>
|
||||||
`;
|
`;
|
||||||
// Add click handler to header eye button
|
// Add click handler to header eye button
|
||||||
const headerEyeBtn = detailsHeader.querySelector('.header-eye-btn');
|
const headerEyeBtn = detailsHeader.querySelector('.header-eye-btn');
|
||||||
@ -2166,7 +2173,7 @@ function setupHoverEvents(sunburstData, contextName = null) {
|
|||||||
<circle cx="12" cy="12" r="3"/>
|
<circle cx="12" cy="12" r="3"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<span class="hover-amount">${sunburstData.total.toLocaleString()} ₽</span>
|
<span class="hover-amount">${formatRUB(sunburstData.total)}\u202F₽</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add click handler to header eye button
|
// Add click handler to header eye button
|
||||||
@ -2518,7 +2525,7 @@ function renderTransactionTable() {
|
|||||||
let value = transaction.originalRow[col];
|
let value = transaction.originalRow[col];
|
||||||
|
|
||||||
if ((col === 'amount_rub' || col === 'amount_original') && typeof value === 'number') {
|
if ((col === 'amount_rub' || col === 'amount_original') && typeof value === 'number') {
|
||||||
value = value.toLocaleString();
|
value = formatRUB(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
td.textContent = value !== undefined && value !== null ? value : '';
|
td.textContent = value !== undefined && value !== null ? value : '';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user