metripy 0.2.7__py3-none-any.whl → 0.3.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of metripy might be problematic. Click here for more details.

Files changed (67) hide show
  1. metripy/Application/Analyzer.py +23 -3
  2. metripy/Application/Application.py +16 -2
  3. metripy/Application/Config/Config.py +34 -0
  4. metripy/Application/Config/File/ConfigFileReaderFactory.py +6 -5
  5. metripy/Application/Config/File/ConfigFileReaderInterface.py +70 -3
  6. metripy/Application/Config/File/JsonConfigFileReader.py +5 -70
  7. metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
  8. metripy/Application/Config/Parser.py +24 -11
  9. metripy/Application/Config/ProjectConfig.py +64 -0
  10. metripy/Application/Info.py +61 -0
  11. metripy/Dependency/Dependency.py +17 -1
  12. metripy/Dependency/Pip/Pip.py +21 -31
  13. metripy/Dependency/Pip/PyPi.py +1 -0
  14. metripy/Git/GitAnalyzer.py +0 -3
  15. metripy/Import/Json/JsonImporter.py +17 -0
  16. metripy/LangAnalyzer/AbstractLangAnalyzer.py +4 -3
  17. metripy/LangAnalyzer/Php/PhpAnalyzer.py +2 -1
  18. metripy/LangAnalyzer/Python/PythonAnalyzer.py +31 -9
  19. metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +55 -0
  20. metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +12 -9
  21. metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +1 -1
  22. metripy/Metric/Code/AggregatedMetrics.py +12 -5
  23. metripy/Metric/Code/FileMetrics.py +32 -1
  24. metripy/Metric/Code/ModuleMetrics.py +5 -5
  25. metripy/Metric/Code/SegmentedMetrics.py +72 -36
  26. metripy/Metric/Code/Segmentor.py +44 -0
  27. metripy/Metric/FileTree/FileTreeParser.py +0 -4
  28. metripy/Metric/Git/GitMetrics.py +1 -1
  29. metripy/Metric/ProjectMetrics.py +29 -0
  30. metripy/Metric/Trend/AggregatedTrendMetric.py +101 -0
  31. metripy/Metric/Trend/ClassTrendMetric.py +20 -0
  32. metripy/Metric/Trend/FileTrendMetric.py +46 -0
  33. metripy/Metric/Trend/FunctionTrendMetric.py +28 -0
  34. metripy/Metric/Trend/SegmentedTrendMetric.py +29 -0
  35. metripy/Report/Html/DependencyPageRenderer.py +21 -0
  36. metripy/Report/Html/FilesPageRenderer.py +28 -0
  37. metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
  38. metripy/Report/Html/IndexPageRenderer.py +47 -0
  39. metripy/Report/Html/PageRenderer.py +43 -0
  40. metripy/Report/Html/PageRendererFactory.py +37 -0
  41. metripy/Report/Html/Reporter.py +78 -137
  42. metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
  43. metripy/Report/Html/TrendsPageRenderer.py +137 -0
  44. metripy/Report/Json/GitJsonReporter.py +3 -0
  45. metripy/Report/Json/JsonReporter.py +6 -2
  46. metripy/Report/ReporterFactory.py +6 -3
  47. metripy/Tree/ClassNode.py +21 -0
  48. metripy/Tree/FunctionNode.py +66 -1
  49. metripy/Trend/TrendAnalyzer.py +150 -0
  50. metripy/templates/html_report/css/styles.css +1386 -0
  51. metripy/templates/html_report/dependencies.html +411 -0
  52. metripy/templates/html_report/files.html +1080 -0
  53. metripy/templates/html_report/git_analysis.html +325 -0
  54. metripy/templates/html_report/images/logo.svg +31 -0
  55. metripy/templates/html_report/index.html +374 -0
  56. metripy/templates/html_report/js/charts.js +313 -0
  57. metripy/templates/html_report/js/dashboard.js +546 -0
  58. metripy/templates/html_report/js/git_analysis.js +383 -0
  59. metripy/templates/html_report/top_offenders.html +267 -0
  60. metripy/templates/html_report/trends.html +468 -0
  61. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/METADATA +27 -9
  62. metripy-0.3.6.dist-info/RECORD +96 -0
  63. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/licenses/LICENSE +1 -1
  64. metripy-0.2.7.dist-info/RECORD +0 -66
  65. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/WHEEL +0 -0
  66. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/entry_points.txt +0 -0
  67. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,374 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Code Metrics Dashboard</title>
7
+ <link rel="stylesheet" href="css/styles.css">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ </head>
11
+ <body>
12
+ <div class="dashboard">
13
+ <!-- Sidebar -->
14
+ <aside class="sidebar">
15
+ <div class="sidebar-header">
16
+ <div class="logo">
17
+ <img src="images/logo.svg" alt="Metripy Logo" class="logo-icon">
18
+ <h2>Metripy</h2>
19
+ </div>
20
+ </div>
21
+
22
+ <nav class="sidebar-nav">
23
+ <ul>
24
+ <li class="nav-item active">
25
+ <a href="#overview" class="nav-link">
26
+ <i class="fas fa-tachometer-alt"></i>
27
+ <span>Overview</span>
28
+ </a>
29
+ </li>
30
+ <li class="nav-item">
31
+ <a href="files.html" class="nav-link">
32
+ <i class="fas fa-file-code"></i>
33
+ <span>Files</span>
34
+ </a>
35
+ </li>
36
+ <li class="nav-item">
37
+ <a href="top_offenders.html" class="nav-link">
38
+ <i class="fa-solid fa-stethoscope"></i>
39
+ <span>Top Offenders</span>
40
+ </a>
41
+ </li>
42
+ <li class="nav-item">
43
+ <a href="git_analysis.html" class="nav-link">
44
+ <i class="fab fa-git-alt"></i>
45
+ <span>Git Analysis</span>
46
+ </a>
47
+ </li>
48
+ <li class="nav-item">
49
+ <a href="dependencies.html" class="nav-link">
50
+ <i class="fas fa-cubes"></i>
51
+ <span>Dependencies</span>
52
+ </a>
53
+ </li>
54
+ <li class="nav-item">
55
+ <a href="trends.html" class="nav-link">
56
+ <i class="fas fa-chart-line"></i>
57
+ <span>Trends</span>
58
+ </a>
59
+ </li>
60
+ </ul>
61
+ </nav>
62
+
63
+ <div class="sidebar-footer">
64
+ <div class="project-info">
65
+ <h4>Project: {{ project_name }}</h4>
66
+ <p>Last updated: {{ last_updated }}</p>
67
+ </div>
68
+ </div>
69
+ </aside>
70
+
71
+ <!-- Main Content -->
72
+ <main class="main-content">
73
+ <header class="page-header">
74
+ <div class="header-content">
75
+ <h1>Code Metrics Overview</h1>
76
+ <p class="subtitle">Comprehensive analysis of your codebase quality and maintainability</p>
77
+ </div>
78
+ </header>
79
+
80
+ <!-- Metrics Cards Row -->
81
+ <section class="metrics-grid">
82
+ <div class="metric-card">
83
+ <div class="metric-header">
84
+ <div class="metric-icon loc">
85
+ <i class="fas fa-file-alt"></i>
86
+ </div>
87
+ <div class="metric-title">
88
+ <h3>Lines of Code</h3>
89
+ <p>Total codebase size</p>
90
+ </div>
91
+ </div>
92
+ <div class="metric-value">
93
+ <span class="value" id="total-loc">{{total_code_metrics.loc}}</span>
94
+ <span class="unit">lines</span>
95
+ {{#IF has_total_code_metrics_trend}}
96
+ <span class="trend-badge {{total_code_metrics_trend.loc_trend_type}}" title="vs last analysis">
97
+ <i class="fas fa-{{total_code_metrics_trend.loc_trend_icon}}"></i>
98
+ <span class="trend-value">{{total_code_metrics_trend.loc_delta}}</span>
99
+ </span>
100
+ {{/IF}}
101
+ </div>
102
+ <div class="metric-segmentation">
103
+ <div class="segment-bar">
104
+ <div class="segment good" data-segment="small"></div>
105
+ <div class="segment okay" data-segment="medium"></div>
106
+ <div class="segment warning" data-segment="large"></div>
107
+ <div class="segment critical" data-segment="very-large"></div>
108
+ </div>
109
+ <div class="segment-labels">
110
+ <div class="segment-label good">
111
+ <div class="segment-count" data-count="small">0</div>
112
+ <div class="segment-text">Small</div>
113
+ </div>
114
+ <div class="segment-label okay">
115
+ <div class="segment-count" data-count="medium">0</div>
116
+ <div class="segment-text">Medium</div>
117
+ </div>
118
+ <div class="segment-label warning">
119
+ <div class="segment-count" data-count="large">0</div>
120
+ <div class="segment-text">Large</div>
121
+ </div>
122
+ <div class="segment-label critical">
123
+ <div class="segment-count" data-count="very-large">0</div>
124
+ <div class="segment-text">Very Large</div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ <div class="metric-description">
129
+ <p>Total size of your codebase. Larger codebases require more maintenance effort and are harder to understand.</p>
130
+ </div>
131
+ </div>
132
+
133
+ <div class="metric-card">
134
+ <div class="metric-header">
135
+ <div class="metric-icon complexity">
136
+ <i class="fas fa-sitemap"></i>
137
+ </div>
138
+ <div class="metric-title">
139
+ <h3>Avg. Complexity</h3>
140
+ <p>Cyclomatic complexity per function</p>
141
+ </div>
142
+ </div>
143
+ <div class="metric-value">
144
+ <span class="value" id="avg-complexity">{{total_code_metrics.avgCcPerFunction}}</span>
145
+ <span class="unit">per function</span>
146
+ {{#IF has_total_code_metrics_trend}}
147
+ <span class="trend-badge {{total_code_metrics_trend.avgCcPerFunction_trend_type}}" title="vs last analysis">
148
+ <i class="fas fa-{{total_code_metrics_trend.avgCcPerFunction_trend_icon}}"></i>
149
+ <span class="trend-value">{{total_code_metrics_trend.avgCcPerFunction_delta}}</span>
150
+ </span>
151
+ {{/IF}}
152
+ </div>
153
+ <div class="metric-segmentation">
154
+ <div class="segment-bar">
155
+ <div class="segment good" data-segment="simple"></div>
156
+ <div class="segment okay" data-segment="moderate"></div>
157
+ <div class="segment warning" data-segment="complex"></div>
158
+ <div class="segment critical" data-segment="very-complex"></div>
159
+ </div>
160
+ <div class="segment-labels">
161
+ <div class="segment-label good">
162
+ <div class="segment-count" data-count="simple">0</div>
163
+ <div class="segment-text">Simple</div>
164
+ </div>
165
+ <div class="segment-label okay">
166
+ <div class="segment-count" data-count="moderate">0</div>
167
+ <div class="segment-text">Moderate</div>
168
+ </div>
169
+ <div class="segment-label warning">
170
+ <div class="segment-count" data-count="complex">0</div>
171
+ <div class="segment-text">Complex</div>
172
+ </div>
173
+ <div class="segment-label critical">
174
+ <div class="segment-count" data-count="very-complex">0</div>
175
+ <div class="segment-text">Very Complex</div>
176
+ </div>
177
+ </div>
178
+ </div>
179
+ <div class="metric-description">
180
+ <p>Lower complexity (1-4) is better. Values above 10 indicate functions that are hard to test and maintain.</p>
181
+ </div>
182
+ </div>
183
+
184
+ <div class="metric-card">
185
+ <div class="metric-header">
186
+ <div class="metric-icon maintainability">
187
+ <i class="fas fa-tools"></i>
188
+ </div>
189
+ <div class="metric-title">
190
+ <h3>Maintainability</h3>
191
+ <p>Maintainability index</p>
192
+ </div>
193
+ </div>
194
+ <div class="metric-value">
195
+ <span class="value" id="maintainability-index">{{total_code_metrics.maintainabilityIndex}}</span>
196
+ <span class="unit">/ 100</span>
197
+ {{#IF has_total_code_metrics_trend}}
198
+ <span class="trend-badge {{total_code_metrics_trend.maintainabilityIndex_trend_type}}" title="vs last analysis">
199
+ <i class="fas fa-{{total_code_metrics_trend.maintainabilityIndex_trend_icon}}"></i>
200
+ <span class="trend-value">{{total_code_metrics_trend.maintainabilityIndex_delta}}</span>
201
+ </span>
202
+ {{/IF}}
203
+ </div>
204
+ <div class="metric-segmentation">
205
+ <div class="segment-bar">
206
+ <div class="segment good" data-segment="excellent"></div>
207
+ <div class="segment okay" data-segment="good"></div>
208
+ <div class="segment warning" data-segment="fair"></div>
209
+ <div class="segment critical" data-segment="poor"></div>
210
+ </div>
211
+ <div class="segment-labels">
212
+ <div class="segment-label good">
213
+ <div class="segment-count" data-count="excellent">0</div>
214
+ <div class="segment-text">Excellent</div>
215
+ </div>
216
+ <div class="segment-label okay">
217
+ <div class="segment-count" data-count="good">0</div>
218
+ <div class="segment-text">Good</div>
219
+ </div>
220
+ <div class="segment-label warning">
221
+ <div class="segment-count" data-count="fair">0</div>
222
+ <div class="segment-text">Fair</div>
223
+ </div>
224
+ <div class="segment-label critical">
225
+ <div class="segment-count" data-count="poor">0</div>
226
+ <div class="segment-text">Poor</div>
227
+ </div>
228
+ </div>
229
+ </div>
230
+ <div class="metric-description">
231
+ <p>Higher is better. Values above 80 indicate highly maintainable code, below 60 suggests refactoring needed.</p>
232
+ </div>
233
+ </div>
234
+
235
+ <div class="metric-card">
236
+ <div class="metric-header">
237
+ <div class="metric-icon avg-loc">
238
+ <i class="fas fa-code"></i>
239
+ </div>
240
+ <div class="metric-title">
241
+ <h3>Avg. Method Size</h3>
242
+ <p>Average lines per method</p>
243
+ </div>
244
+ </div>
245
+ <div class="metric-value">
246
+ <span class="value" id="avg-method-size">{{total_code_metrics.avgLocPerFunction}}</span>
247
+ <span class="unit">lines</span>
248
+ {{#IF has_total_code_metrics_trend}}
249
+ <span class="trend-badge {{total_code_metrics_trend.avgLocPerFunction_trend_type}}" title="vs last analysis">
250
+ <i class="fas fa-{{total_code_metrics_trend.avgLocPerFunction_trend_icon}}"></i>
251
+ <span class="trend-value">{{total_code_metrics_trend.avgLocPerFunction_delta}}</span>
252
+ </span>
253
+ {{/IF}}
254
+ </div>
255
+ <div class="metric-segmentation">
256
+ <div class="segment-bar">
257
+ <div class="segment good" data-segment="concise"></div>
258
+ <div class="segment okay" data-segment="optimal"></div>
259
+ <div class="segment warning" data-segment="large"></div>
260
+ <div class="segment critical" data-segment="too-large"></div>
261
+ </div>
262
+ <div class="segment-labels">
263
+ <div class="segment-label good">
264
+ <div class="segment-count" data-count="concise">0</div>
265
+ <div class="segment-text">Concise</div>
266
+ </div>
267
+ <div class="segment-label okay">
268
+ <div class="segment-count" data-count="optimal">0</div>
269
+ <div class="segment-text">Optimal</div>
270
+ </div>
271
+ <div class="segment-label warning">
272
+ <div class="segment-count" data-count="large">0</div>
273
+ <div class="segment-text">Large</div>
274
+ </div>
275
+ <div class="segment-label critical">
276
+ <div class="segment-count" data-count="too-large">0</div>
277
+ <div class="segment-text">Too Large</div>
278
+ </div>
279
+ </div>
280
+ </div>
281
+ <div class="metric-description">
282
+ <p>Shorter methods (10-20 lines) are easier to understand and test. Methods over 50 lines should be refactored.</p>
283
+ </div>
284
+ </div>
285
+ </section>
286
+
287
+ <!-- Git Commits Chart Row -->
288
+ <section class="chart-section">
289
+ <div class="chart-container">
290
+ <div class="chart-header">
291
+ <h3>Git Activity</h3>
292
+ <p>Commit frequency over time</p>
293
+ </div>
294
+ <div class="chart-content">
295
+ <canvas id="gitCommitsChart"></canvas>
296
+ </div>
297
+ </div>
298
+ <div class="chart-container small">
299
+ <div class="chart-header">
300
+ <h3>License Types</h3>
301
+ <p>Distribution by license</p>
302
+ </div>
303
+ <div class="chart-content">
304
+ <canvas id="licenseTypesChart"></canvas>
305
+ </div>
306
+ </div>
307
+ </section>
308
+
309
+ </main>
310
+ </div>
311
+
312
+ <!-- Scripts -->
313
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
314
+ <script>
315
+ // Embed data directly from template engine to avoid CORS issues
316
+ window.GIT_STATS_DATA = {{git_stats_data}};
317
+ window.METRICS_DATA = {
318
+ totalLoc: {{total_code_metrics.loc}},
319
+ avgComplexity: {{total_code_metrics.avgCcPerFunction}},
320
+ maintainabilityIndex: {{total_code_metrics.maintainabilityIndex}},
321
+ avgMethodSize: {{total_code_metrics.avgLocPerFunction}},
322
+ segmentation: {{segmentation_data}},
323
+ };
324
+ </script>
325
+ <script src="js/dashboard.js"></script>
326
+ <script src="js/charts.js"></script>
327
+ <script>
328
+
329
+
330
+ function createLicenseTypesChart() {
331
+ const canvas = document.getElementById('licenseTypesChart');
332
+ if (!canvas) {
333
+ console.warn('License types chart canvas not found');
334
+ return;
335
+ }
336
+ console.log("creating licenses chart")
337
+ const ctx = canvas.getContext('2d');
338
+ const chartData = {{license_distribution_json}};
339
+ console.log(Chart.version);
340
+ this.charts.licenseTypes = new Chart(ctx, {
341
+ type: 'pie',
342
+ data: {
343
+ labels: Object.keys(chartData),
344
+ datasets: [{
345
+ label: "License Types",
346
+ data: Object.values(chartData),
347
+ hoverOffset: 4
348
+ }]
349
+ },
350
+ options: {
351
+ responsive: true,
352
+ plugins: {
353
+ legend: {
354
+ position: 'top',
355
+ },
356
+ title: {
357
+ display: true,
358
+ text: 'License Types Distribution'
359
+ }
360
+ },
361
+ hover: {
362
+ mode: 'nearest',
363
+ intersect: true
364
+ }
365
+ }
366
+ });
367
+
368
+ // Add chart resize handler
369
+ //this.addResizeHandler('licenseTypes');
370
+ }
371
+ createLicenseTypesChart();
372
+ </script>
373
+ </body>
374
+ </html>
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Charts JavaScript
3
+ * Handles Chart.js functionality for git commits visualization and other charts
4
+ */
5
+
6
+ class ChartManager {
7
+ constructor() {
8
+ this.charts = {};
9
+ this.data = {};
10
+ this.colors = {
11
+ primary: '#3b82f6',
12
+ primaryGradient: {
13
+ start: 'rgba(59, 130, 246, 0.8)',
14
+ end: 'rgba(59, 130, 246, 0.1)'
15
+ },
16
+ success: '#10b981',
17
+ warning: '#f59e0b',
18
+ danger: '#ef4444',
19
+ info: '#06b6d4',
20
+ text: '#1e293b',
21
+ textSecondary: '#64748b',
22
+ border: '#e2e8f0'
23
+ };
24
+ this.init();
25
+ }
26
+
27
+ init() {
28
+ this.setupChartDefaults();
29
+ this.initializeCharts();
30
+ }
31
+
32
+ setupChartDefaults() {
33
+ // Configure Chart.js global defaults
34
+ Chart.defaults.font.family = 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
35
+ Chart.defaults.font.size = 12;
36
+ Chart.defaults.color = this.colors.textSecondary;
37
+ Chart.defaults.borderColor = this.colors.border;
38
+ Chart.defaults.backgroundColor = this.colors.primary;
39
+
40
+ // Configure responsive defaults
41
+ Chart.defaults.responsive = true;
42
+ Chart.defaults.maintainAspectRatio = false;
43
+
44
+ // Configure animation defaults
45
+ /*
46
+ This had the consequence of hindering license chart rendering
47
+ Chart.defaults.animation = {
48
+ duration: 1000,
49
+ easing: 'easeOutQuart'
50
+ };
51
+ */
52
+ }
53
+
54
+ initializeCharts() {
55
+ // Wait for DOM to be ready and data to be available
56
+ this.waitForData().then(() => {
57
+ this.createGitCommitsChart();
58
+ // Add more chart initializations here as needed
59
+ });
60
+ }
61
+
62
+ waitForData() {
63
+ return new Promise((resolve) => {
64
+ const checkData = () => {
65
+ if (window.dashboard && window.dashboard.data && window.dashboard.data.gitCommits) {
66
+ this.data = window.dashboard.data;
67
+ console.log('Git commits data loaded:', this.data.gitCommits.length, 'months');
68
+ resolve();
69
+ } else {
70
+ setTimeout(checkData, 100);
71
+ }
72
+ };
73
+ checkData();
74
+ });
75
+ }
76
+
77
+ createGitCommitsChart() {
78
+ const canvas = document.getElementById('gitCommitsChart');
79
+ if (!canvas) {
80
+ console.warn('Git commits chart canvas not found');
81
+ return;
82
+ }
83
+
84
+ const ctx = canvas.getContext('2d');
85
+
86
+ // Prepare data for the chart
87
+ const chartData = this.prepareGitCommitsData();
88
+
89
+ // Create gradient
90
+ const gradient = ctx.createLinearGradient(0, 0, 0, 300);
91
+ gradient.addColorStop(0, this.colors.primaryGradient.start);
92
+ gradient.addColorStop(1, this.colors.primaryGradient.end);
93
+
94
+ this.charts.gitCommits = new Chart(ctx, {
95
+ type: 'line',
96
+ data: {
97
+ labels: chartData.labels,
98
+ datasets: [{
99
+ label: 'Commits',
100
+ data: chartData.values,
101
+ borderColor: this.colors.primary,
102
+ backgroundColor: gradient,
103
+ borderWidth: 3,
104
+ fill: true,
105
+ tension: 0.4,
106
+ pointBackgroundColor: this.colors.primary,
107
+ pointBorderColor: '#ffffff',
108
+ pointBorderWidth: 2,
109
+ pointRadius: 6,
110
+ pointHoverRadius: 8,
111
+ pointHoverBackgroundColor: this.colors.primary,
112
+ pointHoverBorderColor: '#ffffff',
113
+ pointHoverBorderWidth: 3
114
+ }]
115
+ },
116
+ options: {
117
+ responsive: true,
118
+ maintainAspectRatio: false,
119
+ interaction: {
120
+ intersect: false,
121
+ mode: 'index'
122
+ },
123
+ plugins: {
124
+ legend: {
125
+ display: false
126
+ },
127
+ tooltip: {
128
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
129
+ titleColor: '#ffffff',
130
+ bodyColor: '#ffffff',
131
+ borderColor: this.colors.primary,
132
+ borderWidth: 1,
133
+ cornerRadius: 8,
134
+ displayColors: false,
135
+ titleFont: {
136
+ size: 14,
137
+ weight: 'bold'
138
+ },
139
+ bodyFont: {
140
+ size: 13
141
+ },
142
+ padding: 12,
143
+ callbacks: {
144
+ title: (context) => {
145
+ return context[0].label;
146
+ },
147
+ label: (context) => {
148
+ const value = context.parsed.y;
149
+ const plural = value === 1 ? 'commit' : 'commits';
150
+ return `${value} ${plural}`;
151
+ }
152
+ }
153
+ }
154
+ },
155
+ scales: {
156
+ x: {
157
+ display: true,
158
+ grid: {
159
+ display: false
160
+ },
161
+ border: {
162
+ display: false
163
+ },
164
+ ticks: {
165
+ color: this.colors.textSecondary,
166
+ font: {
167
+ size: 11,
168
+ weight: '500'
169
+ },
170
+ maxRotation: 0,
171
+ padding: 10
172
+ }
173
+ },
174
+ y: {
175
+ display: true,
176
+ beginAtZero: true,
177
+ grid: {
178
+ color: this.colors.border,
179
+ drawBorder: false
180
+ },
181
+ border: {
182
+ display: false
183
+ },
184
+ ticks: {
185
+ color: this.colors.textSecondary,
186
+ font: {
187
+ size: 11,
188
+ weight: '500'
189
+ },
190
+ padding: 10,
191
+ callback: function(value) {
192
+ if (value === 0) return '0';
193
+ return value % 1 === 0 ? value : '';
194
+ }
195
+ }
196
+ }
197
+ },
198
+ animation: {
199
+ duration: 1500,
200
+ easing: 'easeOutQuint'
201
+ },
202
+ elements: {
203
+ point: {
204
+ hoverRadius: 8
205
+ }
206
+ }
207
+ }
208
+ });
209
+
210
+ // Add chart resize handler
211
+ this.addResizeHandler('gitCommits');
212
+ }
213
+
214
+ prepareGitCommitsData() {
215
+ if (!this.data.gitCommits) return { labels: [], values: [] };
216
+
217
+ const data = [...this.data.gitCommits];
218
+
219
+ return {
220
+ labels: data.map(item => item.month),
221
+ values: data.map(item => item.commits)
222
+ };
223
+ }
224
+
225
+
226
+ updateData(newData) {
227
+ this.data.gitCommits = newData;
228
+
229
+ if (this.charts.gitCommits) {
230
+ const chartData = this.prepareGitCommitsData();
231
+
232
+ this.charts.gitCommits.data.labels = chartData.labels;
233
+ this.charts.gitCommits.data.datasets[0].data = chartData.values;
234
+
235
+ this.charts.gitCommits.update('active');
236
+ }
237
+ }
238
+
239
+ addResizeHandler(chartName) {
240
+ if (!this.charts[chartName]) return;
241
+
242
+ const resizeObserver = new ResizeObserver(entries => {
243
+ this.charts[chartName].resize();
244
+ });
245
+
246
+ const canvas = this.charts[chartName].canvas;
247
+ if (canvas && canvas.parentElement) {
248
+ resizeObserver.observe(canvas.parentElement);
249
+ }
250
+ }
251
+
252
+ destroyChart(chartName) {
253
+ if (this.charts[chartName]) {
254
+ this.charts[chartName].destroy();
255
+ delete this.charts[chartName];
256
+ }
257
+ }
258
+
259
+ destroyAllCharts() {
260
+ Object.keys(this.charts).forEach(chartName => {
261
+ this.destroyChart(chartName);
262
+ });
263
+ }
264
+
265
+ // Utility methods
266
+ hexToRgba(hex, alpha = 1) {
267
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
268
+ if (!result) return null;
269
+
270
+ const r = parseInt(result[1], 16);
271
+ const g = parseInt(result[2], 16);
272
+ const b = parseInt(result[3], 16);
273
+
274
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
275
+ }
276
+
277
+ formatNumber(num) {
278
+ if (num >= 1000000) {
279
+ return (num / 1000000).toFixed(1) + 'M';
280
+ } else if (num >= 1000) {
281
+ return (num / 1000).toFixed(1) + 'K';
282
+ }
283
+ return num.toString();
284
+ }
285
+ }
286
+
287
+ // Initialize chart manager when DOM is loaded
288
+ document.addEventListener('DOMContentLoaded', () => {
289
+ window.chartManager = new ChartManager();
290
+ });
291
+
292
+ // Handle page visibility changes to pause/resume animations
293
+ document.addEventListener('visibilitychange', () => {
294
+ if (window.chartManager) {
295
+ Object.values(window.chartManager.charts).forEach(chart => {
296
+ if (document.hidden) {
297
+ chart.stop();
298
+ } else {
299
+ chart.update('none');
300
+ }
301
+ });
302
+ }
303
+ });
304
+
305
+ // Handle window beforeunload to cleanup
306
+ window.addEventListener('beforeunload', () => {
307
+ if (window.chartManager) {
308
+ window.chartManager.destroyAllCharts();
309
+ }
310
+ });
311
+
312
+ // Export for global access
313
+ window.ChartManager = ChartManager;