Skip to content

Commit 4fac2b7

Browse files
Update
1 parent d180fc4 commit 4fac2b7

File tree

1 file changed

+272
-83
lines changed

1 file changed

+272
-83
lines changed

stats_window.py

Lines changed: 272 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
# stats_window.py
22

3-
import tkinter as tk
43
import pandas as pd
54
import matplotlib.pyplot as plt
6-
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
5+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
76
import os
87
import ast
98
import datetime
9+
import numpy as np
10+
from PySide6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
11+
QPushButton, QLabel, QTabWidget, QTableWidget,
12+
QTableWidgetItem, QFrame, QScrollArea, QTextEdit)
13+
from PySide6.QtCore import Qt, QThread, Signal
14+
from PySide6.QtGui import QFont
1015

1116
from utils import read_gitignore, is_ignored, find_projects
1217

@@ -50,8 +55,6 @@ def analyze_project_structure(directory, task_queue):
5055
task_queue.put(('project_stats', structure))
5156

5257

53-
54-
5558
def parse_python_files(projects_dir, export=True, max_files=5000, max_depth=6):
5659
import os, ast, datetime
5760
import pandas as pd
@@ -153,83 +156,269 @@ def parse_python_files(projects_dir, export=True, max_files=5000, max_depth=6):
153156
return df.to_dict("records")
154157

155158

156-
157-
158-
159-
def open_stats_window(root, project_data: list[dict]):
160-
stats_win = tk.Toplevel(root)
161-
stats_win.title("📊 Статистика по проектам")
162-
stats_win.geometry("1000x700")
163-
164-
if not project_data:
165-
tk.Label(stats_win, text="❌ Нет данных для отображения.", font=("Arial", 12)).pack(pady=20)
166-
return
167-
168-
df = pd.DataFrame(project_data)
169-
170-
if df.empty or 'date' not in df.columns:
171-
tk.Label(stats_win, text="❌ Недостаточно данных для статистики.", font=("Arial", 12)).pack(pady=20)
172-
return
173-
174-
df['date'] = pd.to_datetime(df['date'], errors='coerce')
175-
df = df.dropna(subset=['date'])
176-
177-
if df.empty:
178-
tk.Label(stats_win, text="❌ Все даты повреждены или отсутствуют.", font=("Arial", 12)).pack(pady=20)
179-
return
180-
181-
df['month'] = df['date'].dt.to_period('M')
182-
183-
# === Верхняя сводка ===
184-
summary_frame = tk.Frame(stats_win)
185-
summary_frame.pack(pady=10)
186-
187-
total_projects = len(df)
188-
total_files = df['py_count'].sum()
189-
earliest = df['date'].min().strftime("%Y-%m-%d")
190-
latest = df['date'].max().strftime("%Y-%m-%d")
191-
unique_months = df['month'].nunique()
192-
193-
summary_text = (
194-
f"📦 Всего проектов: {total_projects} 🧠 Всего .py файлов: {total_files}\n"
195-
f"📆 Период: {earliest}{latest} 📊 Уникальных месяцев: {unique_months}"
196-
)
197-
tk.Label(summary_frame, text=summary_text, font=("Arial", 11), justify="left").pack()
198-
199-
# === График по месяцам ===
200-
chart_frame = tk.Frame(stats_win)
201-
chart_frame.pack(fill="both", expand=False, pady=5)
202-
203-
fig, ax = plt.subplots(figsize=(8, 4))
204-
df_by_month = df.groupby('month').size().sort_index()
205-
df_by_month.plot(kind='bar', ax=ax, color='skyblue', edgecolor='black')
206-
207-
ax.set_title('📅 Количество проектов по месяцам', fontsize=14)
208-
ax.set_ylabel('Проекты', fontsize=12)
209-
ax.set_xlabel('Месяц', fontsize=12)
210-
ax.grid(True, axis='y', linestyle='--', alpha=0.5)
211-
fig.tight_layout()
212-
213-
canvas = FigureCanvasTkAgg(fig, master=chart_frame)
214-
canvas.draw()
215-
canvas.get_tk_widget().pack()
216-
217-
# === Анализ технологий ===
218-
tech_frame = tk.LabelFrame(stats_win, text="📚 Используемые библиотеки", font=("Arial", 12))
219-
tech_frame.pack(fill="both", expand=True, padx=10, pady=10)
220-
221-
if 'stack' in df.columns:
222-
all_stacks = sum(df['stack'].tolist(), [])
223-
stack_series = pd.Series(all_stacks).value_counts()
224-
if not stack_series.empty:
225-
stack_text = "\n".join(f"• {lib:<20}{count}" for lib, count in stack_series.items())
226-
else:
227-
stack_text = "Нет данных о библиотеках."
228-
else:
229-
stack_text = "Нет данных по стеку технологий."
230-
231-
tk.Message(tech_frame, text=stack_text, font=("Courier New", 10), width=900, anchor="w", justify="left").pack()
232-
233-
# === Кнопка закрытия ===
234-
tk.Button(stats_win, text="Закрыть", command=stats_win.destroy).pack(pady=10)
159+
class StatsWindow(QMainWindow):
160+
def __init__(self, imports_count, parent=None):
161+
super().__init__(parent)
162+
self.imports_count = imports_count
163+
self.init_ui()
164+
self.setup_styles()
165+
166+
def init_ui(self):
167+
self.setWindowTitle("📊 Статистика по проектам")
168+
self.setGeometry(200, 200, 1000, 700)
169+
170+
# Центральный виджет
171+
central_widget = QWidget()
172+
self.setCentralWidget(central_widget)
173+
174+
# Главный layout
175+
main_layout = QVBoxLayout(central_widget)
176+
177+
# Верхняя сводка
178+
summary_frame = QFrame()
179+
summary_layout = QVBoxLayout(summary_frame)
180+
181+
self.summary_label = QLabel("📦 Анализ проектов")
182+
self.summary_label.setAlignment(Qt.AlignCenter)
183+
self.summary_label.setFont(QFont("Arial", 14, QFont.Bold))
184+
summary_layout.addWidget(self.summary_label)
185+
186+
main_layout.addWidget(summary_frame)
187+
188+
# Табы для разных видов статистики
189+
self.tab_widget = QTabWidget()
190+
191+
# Таб с графиками
192+
self.charts_tab = self.create_charts_tab()
193+
self.tab_widget.addTab(self.charts_tab, "📊 Графики")
194+
195+
# Таб с таблицей
196+
self.table_tab = self.create_table_tab()
197+
self.tab_widget.addTab(self.table_tab, "📋 Таблица")
198+
199+
# Таб с деталями
200+
self.details_tab = self.create_details_tab()
201+
self.tab_widget.addTab(self.details_tab, "📝 Детали")
202+
203+
main_layout.addWidget(self.tab_widget)
204+
205+
# Кнопки
206+
button_layout = QHBoxLayout()
207+
208+
self.export_btn = QPushButton("💾 Экспорт в CSV")
209+
self.export_btn.clicked.connect(self.export_to_csv)
210+
211+
self.close_btn = QPushButton("❌ Закрыть")
212+
self.close_btn.clicked.connect(self.close)
213+
214+
button_layout.addWidget(self.export_btn)
215+
button_layout.addStretch()
216+
button_layout.addWidget(self.close_btn)
217+
218+
main_layout.addLayout(button_layout)
219+
220+
def setup_styles(self):
221+
"""Настройка современного стиля"""
222+
self.setStyleSheet("""
223+
QMainWindow {
224+
background-color: #2b2b2b;
225+
color: #ffffff;
226+
}
227+
QTabWidget::pane {
228+
border: 1px solid #444444;
229+
background-color: #1e1e1e;
230+
}
231+
QTabBar::tab {
232+
background-color: #3c3c3c;
233+
color: #ffffff;
234+
padding: 8px 16px;
235+
margin-right: 2px;
236+
}
237+
QTabBar::tab:selected {
238+
background-color: #4a90e2;
239+
}
240+
QPushButton {
241+
background-color: #4a90e2;
242+
color: white;
243+
border: none;
244+
padding: 8px 16px;
245+
border-radius: 4px;
246+
font-weight: bold;
247+
}
248+
QPushButton:hover {
249+
background-color: #357abd;
250+
}
251+
QLabel {
252+
color: #ffffff;
253+
}
254+
QTableWidget {
255+
background-color: #1e1e1e;
256+
color: #ffffff;
257+
gridline-color: #444444;
258+
}
259+
QTextEdit {
260+
background-color: #1e1e1e;
261+
color: #ffffff;
262+
border: 1px solid #444444;
263+
}
264+
""")
265+
266+
def create_charts_tab(self):
267+
"""Создание таба с графиками"""
268+
widget = QWidget()
269+
layout = QVBoxLayout(widget)
270+
271+
# График импортов
272+
self.imports_chart = self.create_imports_chart()
273+
layout.addWidget(self.imports_chart)
274+
275+
return widget
276+
277+
def create_table_tab(self):
278+
"""Создание таба с таблицей"""
279+
widget = QWidget()
280+
layout = QVBoxLayout(widget)
281+
282+
self.table = QTableWidget()
283+
self.populate_table()
284+
layout.addWidget(self.table)
285+
286+
return widget
287+
288+
def create_details_tab(self):
289+
"""Создание таба с деталями"""
290+
widget = QWidget()
291+
layout = QVBoxLayout(widget)
292+
293+
self.details_text = QTextEdit()
294+
self.details_text.setReadOnly(True)
295+
self.populate_details()
296+
layout.addWidget(self.details_text)
297+
298+
return widget
299+
300+
def create_imports_chart(self):
301+
"""Создание графика импортов"""
302+
if not self.imports_count:
303+
label = QLabel("Нет данных для отображения")
304+
label.setAlignment(Qt.AlignCenter)
305+
return label
306+
307+
# Создаем график
308+
fig, ax = plt.subplots(figsize=(10, 6))
309+
310+
# Сортируем данные
311+
sorted_imports = sorted(self.imports_count.items(),
312+
key=lambda x: x[1], reverse=True)[:20]
313+
314+
libraries = [lib for lib, _ in sorted_imports]
315+
counts = [count for _, count in sorted_imports]
316+
317+
# Создаем горизонтальную гистограмму
318+
bars = ax.barh(libraries, counts, color='skyblue', edgecolor='black')
319+
320+
# Настройки графика
321+
ax.set_xlabel('Количество использований')
322+
ax.set_title('Топ-20 используемых библиотек')
323+
ax.grid(True, axis='x', linestyle='--', alpha=0.7)
324+
325+
# Добавляем значения на столбцы
326+
for i, (bar, count) in enumerate(zip(bars, counts)):
327+
ax.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height()/2,
328+
str(count), va='center')
329+
330+
plt.tight_layout()
331+
332+
# Создаем canvas для Qt
333+
canvas = FigureCanvas(fig)
334+
return canvas
335+
336+
def populate_table(self):
337+
"""Заполнение таблицы данными"""
338+
if not self.imports_count:
339+
return
340+
341+
# Настройка таблицы
342+
sorted_imports = sorted(self.imports_count.items(),
343+
key=lambda x: x[1], reverse=True)
344+
345+
self.table.setRowCount(len(sorted_imports))
346+
self.table.setColumnCount(3)
347+
self.table.setHorizontalHeaderLabels(['Библиотека', 'Количество', '%'])
348+
349+
total = sum(self.imports_count.values())
350+
351+
for i, (lib, count) in enumerate(sorted_imports):
352+
percentage = (count / total) * 100
353+
354+
self.table.setItem(i, 0, QTableWidgetItem(lib))
355+
self.table.setItem(i, 1, QTableWidgetItem(str(count)))
356+
self.table.setItem(i, 2, QTableWidgetItem(f"{percentage:.2f}%"))
357+
358+
self.table.resizeColumnsToContents()
359+
360+
def populate_details(self):
361+
"""Заполнение детальной информации"""
362+
if not self.imports_count:
363+
self.details_text.setText("Нет данных для отображения")
364+
return
365+
366+
total = sum(self.imports_count.values())
367+
unique_libs = len(self.imports_count)
368+
369+
details = f"""
370+
📊 ДЕТАЛЬНАЯ СТАТИСТИКА ИМПОРТОВ
371+
372+
📈 Общая информация:
373+
• Всего импортов: {total:,}
374+
• Уникальных библиотек: {unique_libs}
375+
• Среднее использование: {total/unique_libs:.1f}
376+
377+
🏆 Топ-10 библиотек:
378+
"""
379+
380+
sorted_imports = sorted(self.imports_count.items(),
381+
key=lambda x: x[1], reverse=True)[:10]
382+
383+
for i, (lib, count) in enumerate(sorted_imports, 1):
384+
percentage = (count / total) * 100
385+
details += f"{i:2d}. {lib:<20} {count:>6} ({percentage:>5.1f}%)\n"
386+
387+
self.details_text.setText(details)
388+
389+
def export_to_csv(self):
390+
"""Экспорт данных в CSV"""
391+
if not self.imports_count:
392+
return
393+
394+
import pandas as pd
395+
396+
# Создаем DataFrame
397+
data = []
398+
total = sum(self.imports_count.values())
399+
400+
for lib, count in self.imports_count.items():
401+
percentage = (count / total) * 100
402+
data.append({
403+
'Библиотека': lib,
404+
'Количество': count,
405+
'Процент': round(percentage, 2)
406+
})
407+
408+
df = pd.DataFrame(data)
409+
df = df.sort_values('Количество', ascending=False)
410+
411+
# Сохраняем файл
412+
filename = f"imports_analysis_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
413+
df.to_csv(filename, index=False, encoding='utf-8-sig')
414+
415+
from PySide6.QtWidgets import QMessageBox
416+
QMessageBox.information(self, "Экспорт", f"Данные сохранены в файл: {filename}")
417+
418+
419+
def open_stats_window(parent, imports_count):
420+
"""Функция для открытия окна статистики"""
421+
stats_window = StatsWindow(imports_count, parent)
422+
stats_window.show()
423+
return stats_window
235424

0 commit comments

Comments
 (0)