diff --git a/src/charts/chart4.js b/src/charts/chart4.js
index dfd7984..eaa7a25 100644
--- a/src/charts/chart4.js
+++ b/src/charts/chart4.js
@@ -1,9 +1,8 @@
/**
* Chart 4 — Сравнение регионов: две вертикальные панели рядом.
*
- * Левая панель: зелёные бары (лидеры).
- * Правая панель: оранжевые/светлые бары (аутсайдеры).
- * Выбранный субъект: жёлтый/оранжевый highlight (isHighlight=true).
+ * Обе панели с Y-осью и сеткой. Общая шкала Y.
+ * Левая: зелёные бары. Правая: оранжевые. isHighlight=true → жёлтый.
*
* Параметры:
* left: { title, bars: [{ label, value, isHighlight? }] }
@@ -14,92 +13,94 @@
const sharp = require('sharp')
const { PALETTE } = require('../data/palette')
-const { dark: DARK, mdGray: MDGRAY, ltGray: LTGRAY, grid: GRID, green: GREEN, orange: ORANGE, o: YELLOW } = PALETTE
+const { dark: DARK, mdGray: MDGRAY, ltGray: LTGRAY, grid: GRID,
+ green: GREEN, orange: ORANGE, o: YELLOW } = PALETTE
function esc (s) { return String(s).replace(/&/g,'&').replace(//g,'>') }
async function chart4_regionCompare ({ left, right, regionLabel, unit = 'руб./кг', width = 920 }) {
const W = width
- const gap = 48
- const panelW = (W - gap) / 2
- const PL = 28, PR = 12, PT = 50, PB = 64
- const cH = 260
+ const DIVIDER_GAP = 52 // зазор между панелями (пунктирная линия посредине)
+ const PL = 36, PR = 10 // отступы внутри каждой панели
+ const PT = 56, PB = 64 // верх/низ (PB — место для подписей оси X + unit)
+ const cH = 280 // высота области баров
- // Общая шкала Y для честного сравнения
- const allVals = [...left.bars.map(b => b.value), ...right.bars.map(b => b.value)]
- const maxV = Math.max(...allVals) * 1.12
- const minV = 0
const H = PT + cH + PB
+ const panelW = (W - DIVIDER_GAP) / 2
+ const cWp = panelW - PL - PR // ширина области баров в каждой панели
+
+ // Общая шкала Y — берём max по обеим панелям
+ const allVals = [...left.bars.map(b => b.value), ...right.bars.map(b => b.value)]
+ const rawMax = Math.max(...allVals)
+ const rawMin = 0
+ // Красивые деления
+ const tickStep = rawMax > 40 ? 10 : rawMax > 20 ? 5 : 2
+ const maxV = Math.ceil(rawMax / tickStep) * tickStep + tickStep
+ const minV = 0
const yV = v => PT + cH - (v - minV) / (maxV - minV) * cH
- function renderPanel (bars, offsetX, title, defaultColor, isLeftPanel) {
+ // Рисуем одну панель
+ function renderPanel (bars, offsetX, title, defaultColor, showYLabels) {
const n = bars.length
- const cW = panelW - PL - PR
- const slotW = cW / n
- const barW = Math.min(50, slotW * 0.68)
+ const slotW = cWp / n
+ const barW = Math.min(52, slotW * 0.68)
let s = ''
- // Заголовок
- s += `${esc(title)}`
+ // Заголовок панели
+ s += `${esc(title)}`
- // Y grid + labels (только у левой панели)
- if (isLeftPanel) {
- const step = maxV > 50 ? 10 : 5
- for (let v = 0; v <= maxV; v += step) {
- const y = yV(v)
- s += ``
- s += `${v}`
- }
- } else {
- const step = maxV > 50 ? 10 : 5
- for (let v = 0; v <= maxV; v += step) {
- const y = yV(v)
- s += ``
+ // Y grid + лейблы
+ for (let v = 0; v <= maxV; v += tickStep) {
+ const y = yV(v)
+ s += ``
+ if (showYLabels) {
+ s += `${v}`
}
}
- // Ось X внизу
- s += ``
+ // Ось X (нулевая)
+ s += ``
+ // Бары
bars.forEach((b, i) => {
const cx = offsetX + PL + i * slotW + slotW / 2
const x = cx - barW / 2
const bH = Math.abs(yV(b.value) - yV(0))
const by = yV(b.value)
- // Цвет: highlight → жёлтый, иначе — defaultColor
const color = b.isHighlight ? YELLOW : defaultColor
s += ``
// Значение над баром
- s += `${b.value.toFixed(1)}`
+ s += `${b.value.toFixed(1)}`
// Подпись под осью (повёрнутая)
- const labelX = cx, labelY = yV(0) + 12
- const maxLen = Math.floor(slotW / 4.8)
+ const labelX = cx
+ const labelY = yV(0) + 12
+ const maxLen = Math.floor(slotW / 4.6)
const name = b.label.length > maxLen ? b.label.slice(0, maxLen) + '…' : b.label
s += `${esc(name)}`
})
- // Подпись единиц
- s += `${esc(unit)}`
+ // Подпись единиц под панелью
+ s += `${esc(unit)}`
return s
}
- const leftSvg = renderPanel(left.bars, 0, left.title, GREEN, true)
- const rightSvg = renderPanel(right.bars, panelW + gap, right.title, ORANGE, false)
+ const leftSvg = renderPanel(left.bars, 0, left.title, GREEN, true)
+ const rightSvg = renderPanel(right.bars, panelW + DIVIDER_GAP, right.title, ORANGE, false)
- // Разделительная линия между панелями
- const divX = panelW + gap / 2
- const dividerLine = ``
+ // Пунктирный разделитель между панелями
+ const divX = panelW + DIVIDER_GAP / 2
+ const divider = ``
const fullSvg = ``
return {