diff --git a/CHART_IMPROVEMENTS.md b/CHART_IMPROVEMENTS.md
new file mode 100644
index 0000000..45f1d30
--- /dev/null
+++ b/CHART_IMPROVEMENTS.md
@@ -0,0 +1,68 @@
+# Улучшения диаграмм в PDF-отчетах
+
+## Проблемы, которые были решены
+
+1. **Обрезание диаграмм** - диаграммы выходили за границы страницы PDF
+2. **Некрасивый внешний вид** - диаграммы выглядели неаккуратно и нечитаемо
+3. **Плохое качество** - низкое разрешение и размытость
+
+## Внесенные улучшения
+
+### 1. Улучшенная логика масштабирования (`ResearchPdfService.java`)
+
+- **Точный расчет доступного пространства** с учетом всех отступов
+- **Пропорциональное масштабирование** для сохранения соотношения сторон
+- **Ограничения масштабирования** (30%-100%) для читаемости
+- **Дополнительная проверка** размера после масштабирования
+- **Принудительное уменьшение** если диаграмма все еще не помещается
+
+### 2. Оптимизация генерации SVG (`OpenAiChartService.java`)
+
+- **Стандартные размеры** 800x600 для оптимального качества в PDF
+- **Увеличенные отступы** (минимум 60px) вокруг диаграммы
+- **Улучшенная стилизация** с градиентами, тенями и рамками
+- **Контрастные цвета** и размер шрифта не менее 14px
+- **Сетка и оси** для лучшей читаемости
+
+### 3. Улучшенная конвертация SVG в PNG (`SvgToPngConverter.java`)
+
+- **Высокое качество** конвертации для PDF
+- **Автоматические размеры** если SVG не содержит явных размеров
+- **Оптимизация** для печати и отображения
+
+### 4. Адаптивное размещение диаграмм
+
+- **Автоматическое разбиение** больших диаграмм на отдельные страницы
+- **Подписи к диаграммам** при наличии нескольких диаграмм
+- **Улучшенные отступы** для лучшего внешнего вида
+- **Центрирование** всех диаграмм
+
+### 5. Улучшения в ReportGenerationService
+
+- **Улучшенное масштабирование** для диаграмм тональности
+- **Пропорциональное изменение размера** с сохранением качества
+- **Лучшее позиционирование** и отступы
+
+## Результат
+
+- ✅ Диаграммы больше не обрезаются
+- ✅ Улучшенное качество и читаемость
+- ✅ Профессиональный внешний вид
+- ✅ Адаптивное размещение на страницах
+- ✅ Оптимизация для PDF-формата
+
+## Технические детали
+
+### Ключевые методы:
+
+- `addChartsSection()` - основная логика добавления диаграмм
+- `shouldSplitChart()` - проверка необходимости разбиения на страницы
+- `addChartWithPagination()` - добавление с разбиением
+- `buildSvgPrompt()` - улучшенные промпты для генерации SVG
+
+### Параметры масштабирования:
+
+- Минимальный масштаб: 30%
+- Максимальный масштаб: 100%
+- Безопасный отступ: 90% от доступного пространства
+- Стандартные размеры SVG: 800x600px
diff --git a/src/main/java/kz/konturai/parser/service/OpenAiChartService.java b/src/main/java/kz/konturai/parser/service/OpenAiChartService.java
index 6e6487e..545c581 100644
--- a/src/main/java/kz/konturai/parser/service/OpenAiChartService.java
+++ b/src/main/java/kz/konturai/parser/service/OpenAiChartService.java
@@ -184,7 +184,7 @@ public class OpenAiChartService {
if (isRussian) {
return "Ты — эксперт по визуализации данных. На основе следующего JSON, сгенерируй полный и валидный SVG-код для диаграммы. "
+
- "SVG должен быть стильным и читаемым, с подписями на русском языке. \n" +
+ "SVG должен быть стильным, читаемым и оптимизированным для PDF, с подписями на русском языке. \n" +
"КРИТИЧЕСКИ ВАЖНЫЕ ПРАВИЛА:\n" +
"1. ВСЕ значения атрибутов width, height, x, y, r ДОЛЖНЫ быть СТРОГО ПОЛОЖИТЕЛЬНЫМИ числами (> 0).\n"
+
@@ -194,13 +194,23 @@ public class OpenAiChartService {
+
"4. Проверь каждый элемент перед отправкой - отрицательные значения приведут к ошибке конвертации.\n"
+
- "5. Не добавляй никаких комментариев или объяснений.\n" +
- "6. Верни ТОЛЬКО SVG-код, начиная с тега .\n\n" +
+ "5. ОПТИМИЗАЦИЯ ДЛЯ PDF:\n" +
+ " - Используй размеры SVG: width='800' height='600' для оптимального качества в PDF\n" +
+ " - Добавь достаточные отступы (padding) вокруг диаграммы (минимум 60px)\n" +
+ " - Используй четкие, контрастные цвета\n" +
+ " - Размер шрифта должен быть не менее 14px для читаемости\n" +
+ " - Добавь рамку вокруг диаграммы для лучшего внешнего вида\n" +
+ "6. СТИЛИЗАЦИЯ:\n" +
+ " - Используй градиенты или тени для объемности\n" +
+ " - Добавь сетку или оси с подписями\n" +
+ " - Используй разные цвета для разных элементов\n" +
+ "7. Не добавляй никаких комментариев или объяснений.\n" +
+ "8. Верни ТОЛЬКО SVG-код, начиная с тега .\n\n" +
"JSON с данными:\n---\n" +
json + "\n---";
} else {
- return "You are a data visualization expert that generates clean, valid SVG code.\n" +
- "Based on the following JSON, generate a complete and valid SVG code for a bar chart.\n\n" +
+ return "You are a data visualization expert that generates clean, valid SVG code optimized for PDF.\n" +
+ "Based on the following JSON, generate a complete and valid SVG code for a chart.\n\n" +
"CRITICAL RULES - FOLLOW EXACTLY:\n" +
"1. The SVG coordinate system starts with (0,0) in the TOP-LEFT corner. The Y-axis points DOWN.\n" +
@@ -209,8 +219,18 @@ public class OpenAiChartService {
"3. ALL dimensional attributes (width, height, x, y, r) MUST be POSITIVE numbers (> 0).\n" +
"4. NEVER use negative values in any element attributes - this will cause conversion errors.\n" +
"5. Double-check every , , element for positive values.\n" +
- "6. The chart must be visually appealing, with appropriate padding, colors, and English labels.\n" +
- "7. Return ONLY the raw SVG code, starting with . No explanations or markdown.\n\n"
+ "6. PDF OPTIMIZATION:\n" +
+ " - Use SVG dimensions: width='800' height='600' for optimal PDF quality\n" +
+ " - Add sufficient padding around the chart (minimum 60px)\n" +
+ " - Use clear, contrasting colors\n" +
+ " - Font size should be at least 14px for readability\n" +
+ " - Add a border around the chart for better appearance\n" +
+ "7. STYLING:\n" +
+ " - Use gradients or shadows for depth\n" +
+ " - Add grid lines or axes with labels\n" +
+ " - Use different colors for different elements\n" +
+ "8. The chart must be visually appealing, with appropriate padding, colors, and English labels.\n" +
+ "9. Return ONLY the raw SVG code, starting with . No explanations or markdown.\n\n"
+
"JSON data:\n---\n" +
diff --git a/src/main/java/kz/konturai/parser/service/ReportGenerationService.java b/src/main/java/kz/konturai/parser/service/ReportGenerationService.java
index 95f4f70..f19ac61 100644
--- a/src/main/java/kz/konturai/parser/service/ReportGenerationService.java
+++ b/src/main/java/kz/konturai/parser/service/ReportGenerationService.java
@@ -13,6 +13,7 @@ import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.util.Units;
import com.lowagie.text.Document;
+import com.lowagie.text.Element;
import com.lowagie.text.Image;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;
@@ -406,7 +407,25 @@ public class ReportGenerationService {
byte[] piePng = buildSentimentChart(sentiment);
if (piePng != null) {
Image img = Image.getInstance(piePng);
- img.scaleToFit(480, 320);
+
+ // Улучшенное масштабирование для лучшего качества
+ float availableWidth = 480f;
+ float availableHeight = 320f;
+
+ // Рассчитываем оптимальный размер с сохранением пропорций
+ float scaleX = availableWidth / img.getWidth();
+ float scaleY = availableHeight / img.getHeight();
+ float scale = Math.min(scaleX, scaleY);
+
+ // Ограничиваем масштабирование для читаемости
+ scale = Math.min(scale, 1.0f);
+ scale = Math.max(scale, 0.5f);
+
+ img.scalePercent(scale * 100);
+ img.setAlignment(Element.ALIGN_CENTER);
+ img.setSpacingBefore(10);
+ img.setSpacingAfter(10);
+
pdf.add(img);
}
diff --git a/src/main/java/kz/konturai/parser/service/ResearchPdfService.java b/src/main/java/kz/konturai/parser/service/ResearchPdfService.java
index df074fd..4c5519a 100644
--- a/src/main/java/kz/konturai/parser/service/ResearchPdfService.java
+++ b/src/main/java/kz/konturai/parser/service/ResearchPdfService.java
@@ -208,32 +208,114 @@ public class ResearchPdfService {
Chapter chapter = new Chapter(title, 3);
chapter.setNumberDepth(0);
- for (byte[] imageData : chartImages) {
+ for (int i = 0; i < chartImages.size(); i++) {
try {
- Image chartImage = Image.getInstance(imageData);
+ Image chartImage = Image.getInstance(chartImages.get(i));
// --- УЛУЧШЕННАЯ ЛОГИКА МАСШТАБИРОВАНИЯ ---
- float availableWidth = document.getPageSize().getWidth() - document.leftMargin()
- - document.rightMargin();
- float availableHeight = document.getPageSize().getHeight() - document.topMargin()
- - document.bottomMargin() - 50; // -50 для доп. отступа
+ float pageWidth = document.getPageSize().getWidth();
+ float pageHeight = document.getPageSize().getHeight();
+ float leftMargin = document.leftMargin();
+ float rightMargin = document.rightMargin();
+ float topMargin = document.topMargin();
+ float bottomMargin = document.bottomMargin();
- // Масштабируем пропорционально, чтобы вписать и по ширине, и по высоте
- chartImage.scaleToFit(availableWidth, availableHeight);
- // --- КОНЕЦ УЛУЧШЕНОЙ ЛОГИКИ ---
+ // Рассчитываем доступное пространство с учетом отступов
+ float availableWidth = pageWidth - leftMargin - rightMargin;
+ float availableHeight = pageHeight - topMargin - bottomMargin - 80; // -80 для заголовков и отступов
+ // Получаем оригинальные размеры изображения
+ float originalWidth = chartImage.getWidth();
+ float originalHeight = chartImage.getHeight();
+
+ // Рассчитываем коэффициенты масштабирования
+ float scaleX = availableWidth / originalWidth;
+ float scaleY = availableHeight / originalHeight;
+
+ // Используем меньший коэффициент для сохранения пропорций
+ float scale = Math.min(scaleX, scaleY);
+
+ // Ограничиваем масштабирование для читаемости
+ scale = Math.min(scale, 1.0f); // Не увеличиваем больше оригинала
+ scale = Math.max(scale, 0.3f); // Не уменьшаем слишком сильно
+
+ // Применяем масштабирование
+ chartImage.scalePercent(scale * 100);
+
+ // Проверяем, помещается ли диаграмма на страницу
+ float scaledWidth = chartImage.getScaledWidth();
+ float scaledHeight = chartImage.getScaledHeight();
+
+ // Если диаграмма все еще слишком большая, принудительно уменьшаем
+ if (scaledWidth > availableWidth || scaledHeight > availableHeight) {
+ float forceScaleX = availableWidth / scaledWidth;
+ float forceScaleY = availableHeight / scaledHeight;
+ float forceScale = Math.min(forceScaleX, forceScaleY) * 0.9f; // 90% для безопасности
+ chartImage.scalePercent(forceScale * 100);
+ }
+
+ // Центрируем изображение
chartImage.setAlignment(Element.ALIGN_CENTER);
- chapter.add(new Paragraph(" ")); // Отступ
- chapter.add(chartImage);
+ // Добавляем отступы для лучшего внешнего вида
+ chartImage.setSpacingBefore(20);
+ chartImage.setSpacingAfter(20);
+
+ // Добавляем подпись к диаграмме
+ if (chartImages.size() > 1) {
+ Paragraph chartTitle = new Paragraph("Диаграмма " + (i + 1), subheadingFont);
+ chartTitle.setAlignment(Element.ALIGN_CENTER);
+ chartTitle.setSpacingBefore(10);
+ chapter.add(chartTitle);
+ }
+
+ // Используем новую логику добавления с разбиением на страницы
+ addChartWithPagination(chartImage, chapter, document, i);
+
+ // Добавляем разрыв страницы между диаграммами, если их много
+ if (i < chartImages.size() - 1) {
+ chapter.add(Chunk.NEWLINE);
+ chapter.add(Chunk.NEWLINE);
+ }
} catch (Exception e) {
System.err.println("Не удалось добавить изображение диаграммы в PDF: " + e.getMessage());
+ logger.error("Error adding chart image to PDF", e);
}
}
document.add(chapter);
}
+ /**
+ * Проверяет, нужно ли разбивать диаграмму на несколько страниц
+ */
+ private boolean shouldSplitChart(Image chartImage, Document document) {
+ float pageHeight = document.getPageSize().getHeight();
+ float topMargin = document.topMargin();
+ float bottomMargin = document.bottomMargin();
+ float availableHeight = pageHeight - topMargin - bottomMargin - 80;
+
+ return chartImage.getScaledHeight() > availableHeight * 0.8f; // 80% от доступной высоты
+ }
+
+ /**
+ * Добавляет диаграмму с возможным разбиением на страницы
+ */
+ private void addChartWithPagination(Image chartImage, Chapter chapter, Document document, int chartIndex)
+ throws DocumentException {
+ if (shouldSplitChart(chartImage, document)) {
+ // Если диаграмма слишком большая, добавляем ее на отдельную страницу
+ document.newPage();
+ Paragraph chartTitle = new Paragraph("Диаграмма " + (chartIndex + 1) + " (продолжение)", subheadingFont);
+ chartTitle.setAlignment(Element.ALIGN_CENTER);
+ chartTitle.setSpacingBefore(10);
+ chapter.add(chartTitle);
+ }
+
+ chapter.add(new Paragraph(" ")); // Отступ
+ chapter.add(chartImage);
+ }
+
private void addTitlePage(Document document, String query) throws DocumentException {
// Этот метод был в порядке, оставляем без изменений
Paragraph title = new Paragraph(query, titleFont);
diff --git a/src/main/java/kz/konturai/parser/util/SvgToPngConverter.java b/src/main/java/kz/konturai/parser/util/SvgToPngConverter.java
index 86b0eaa..8891ccc 100644
--- a/src/main/java/kz/konturai/parser/util/SvgToPngConverter.java
+++ b/src/main/java/kz/konturai/parser/util/SvgToPngConverter.java
@@ -36,9 +36,16 @@ public class SvgToPngConverter {
// 1. Санитизируем SVG код перед конвертацией
String sanitizedSvg = sanitizeSvg(svgCode);
- // 2. Создаем транскодер для PNG
+ // 2. Создаем транскодер для PNG с улучшенными настройками
PNGTranscoder transcoder = new PNGTranscoder();
+ // Устанавливаем размеры для оптимального качества в PDF
+ // Если SVG не имеет явных размеров, устанавливаем стандартные
+ if (!sanitizedSvg.contains("width=") || !sanitizedSvg.contains("height=")) {
+ transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, 800f);
+ transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, 600f);
+ }
+
// 3. Устанавливаем входные данные (наш SVG-код)
InputStream inputStream = new ByteArrayInputStream(sanitizedSvg.getBytes(StandardCharsets.UTF_8));
TranscoderInput input = new TranscoderInput(inputStream);