panelbox 0.2.0__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.
- panelbox/__init__.py +67 -0
- panelbox/__version__.py +14 -0
- panelbox/cli/__init__.py +0 -0
- panelbox/cli/{commands}/__init__.py +0 -0
- panelbox/core/__init__.py +0 -0
- panelbox/core/base_model.py +164 -0
- panelbox/core/formula_parser.py +318 -0
- panelbox/core/panel_data.py +387 -0
- panelbox/core/results.py +366 -0
- panelbox/datasets/__init__.py +0 -0
- panelbox/datasets/{data}/__init__.py +0 -0
- panelbox/gmm/__init__.py +65 -0
- panelbox/gmm/difference_gmm.py +645 -0
- panelbox/gmm/estimator.py +562 -0
- panelbox/gmm/instruments.py +580 -0
- panelbox/gmm/results.py +550 -0
- panelbox/gmm/system_gmm.py +621 -0
- panelbox/gmm/tests.py +535 -0
- panelbox/models/__init__.py +11 -0
- panelbox/models/dynamic/__init__.py +0 -0
- panelbox/models/iv/__init__.py +0 -0
- panelbox/models/static/__init__.py +13 -0
- panelbox/models/static/fixed_effects.py +516 -0
- panelbox/models/static/pooled_ols.py +298 -0
- panelbox/models/static/random_effects.py +512 -0
- panelbox/report/__init__.py +61 -0
- panelbox/report/asset_manager.py +410 -0
- panelbox/report/css_manager.py +472 -0
- panelbox/report/exporters/__init__.py +15 -0
- panelbox/report/exporters/html_exporter.py +440 -0
- panelbox/report/exporters/latex_exporter.py +510 -0
- panelbox/report/exporters/markdown_exporter.py +446 -0
- panelbox/report/renderers/__init__.py +11 -0
- panelbox/report/renderers/static/__init__.py +0 -0
- panelbox/report/renderers/static_validation_renderer.py +341 -0
- panelbox/report/report_manager.py +502 -0
- panelbox/report/template_manager.py +337 -0
- panelbox/report/transformers/__init__.py +0 -0
- panelbox/report/transformers/static/__init__.py +0 -0
- panelbox/report/validation_transformer.py +449 -0
- panelbox/standard_errors/__init__.py +0 -0
- panelbox/templates/__init__.py +0 -0
- panelbox/templates/assets/css/base_styles.css +382 -0
- panelbox/templates/assets/css/report_components.css +747 -0
- panelbox/templates/assets/js/tab-navigation.js +161 -0
- panelbox/templates/assets/js/utils.js +276 -0
- panelbox/templates/common/footer.html +24 -0
- panelbox/templates/common/header.html +44 -0
- panelbox/templates/common/meta.html +5 -0
- panelbox/templates/validation/interactive/index.html +272 -0
- panelbox/templates/validation/interactive/partials/charts.html +58 -0
- panelbox/templates/validation/interactive/partials/methodology.html +201 -0
- panelbox/templates/validation/interactive/partials/overview.html +146 -0
- panelbox/templates/validation/interactive/partials/recommendations.html +101 -0
- panelbox/templates/validation/interactive/partials/test_results.html +231 -0
- panelbox/utils/__init__.py +0 -0
- panelbox/utils/formatting.py +172 -0
- panelbox/utils/matrix_ops.py +233 -0
- panelbox/utils/statistical.py +173 -0
- panelbox/validation/__init__.py +58 -0
- panelbox/validation/base.py +175 -0
- panelbox/validation/cointegration/__init__.py +0 -0
- panelbox/validation/cross_sectional_dependence/__init__.py +13 -0
- panelbox/validation/cross_sectional_dependence/breusch_pagan_lm.py +222 -0
- panelbox/validation/cross_sectional_dependence/frees.py +297 -0
- panelbox/validation/cross_sectional_dependence/pesaran_cd.py +188 -0
- panelbox/validation/heteroskedasticity/__init__.py +13 -0
- panelbox/validation/heteroskedasticity/breusch_pagan.py +222 -0
- panelbox/validation/heteroskedasticity/modified_wald.py +172 -0
- panelbox/validation/heteroskedasticity/white.py +208 -0
- panelbox/validation/instruments/__init__.py +0 -0
- panelbox/validation/robustness/__init__.py +0 -0
- panelbox/validation/serial_correlation/__init__.py +13 -0
- panelbox/validation/serial_correlation/baltagi_wu.py +220 -0
- panelbox/validation/serial_correlation/breusch_godfrey.py +260 -0
- panelbox/validation/serial_correlation/wooldridge_ar.py +200 -0
- panelbox/validation/specification/__init__.py +16 -0
- panelbox/validation/specification/chow.py +273 -0
- panelbox/validation/specification/hausman.py +264 -0
- panelbox/validation/specification/mundlak.py +331 -0
- panelbox/validation/specification/reset.py +273 -0
- panelbox/validation/unit_root/__init__.py +0 -0
- panelbox/validation/validation_report.py +257 -0
- panelbox/validation/validation_suite.py +401 -0
- panelbox-0.2.0.dist-info/METADATA +337 -0
- panelbox-0.2.0.dist-info/RECORD +90 -0
- panelbox-0.2.0.dist-info/WHEEL +5 -0
- panelbox-0.2.0.dist-info/entry_points.txt +2 -0
- panelbox-0.2.0.dist-info/licenses/LICENSE +21 -0
- panelbox-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PanelBox Reports - Tab Navigation
|
|
3
|
+
* ==================================
|
|
4
|
+
*
|
|
5
|
+
* Handles tab switching for interactive reports.
|
|
6
|
+
* Lightweight vanilla JavaScript implementation.
|
|
7
|
+
*
|
|
8
|
+
* Version: 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
(function() {
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize tab navigation for all tab containers
|
|
16
|
+
*/
|
|
17
|
+
function initTabs() {
|
|
18
|
+
const tabContainers = document.querySelectorAll('.tabs-container');
|
|
19
|
+
|
|
20
|
+
tabContainers.forEach(container => {
|
|
21
|
+
const tabButtons = container.querySelectorAll('.tab-button');
|
|
22
|
+
const tabContents = container.querySelectorAll('.tab-content');
|
|
23
|
+
|
|
24
|
+
// Set up click handlers
|
|
25
|
+
tabButtons.forEach(button => {
|
|
26
|
+
button.addEventListener('click', function() {
|
|
27
|
+
const targetId = this.dataset.tab;
|
|
28
|
+
switchTab(container, targetId);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Activate first tab by default
|
|
33
|
+
if (tabButtons.length > 0) {
|
|
34
|
+
const firstTab = tabButtons[0].dataset.tab;
|
|
35
|
+
switchTab(container, firstTab);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Switch to a specific tab
|
|
42
|
+
* @param {HTMLElement} container - The tabs container element
|
|
43
|
+
* @param {string} targetId - The ID of the tab to activate
|
|
44
|
+
*/
|
|
45
|
+
function switchTab(container, targetId) {
|
|
46
|
+
const tabButtons = container.querySelectorAll('.tab-button');
|
|
47
|
+
const tabContents = container.querySelectorAll('.tab-content');
|
|
48
|
+
|
|
49
|
+
// Deactivate all tabs
|
|
50
|
+
tabButtons.forEach(btn => btn.classList.remove('active'));
|
|
51
|
+
tabContents.forEach(content => content.classList.remove('active'));
|
|
52
|
+
|
|
53
|
+
// Activate target tab
|
|
54
|
+
const targetButton = container.querySelector(`[data-tab="${targetId}"]`);
|
|
55
|
+
const targetContent = document.getElementById(targetId);
|
|
56
|
+
|
|
57
|
+
if (targetButton && targetContent) {
|
|
58
|
+
targetButton.classList.add('active');
|
|
59
|
+
targetContent.classList.add('active');
|
|
60
|
+
|
|
61
|
+
// Scroll tab into view if needed
|
|
62
|
+
targetButton.scrollIntoView({
|
|
63
|
+
behavior: 'smooth',
|
|
64
|
+
block: 'nearest',
|
|
65
|
+
inline: 'center'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Dispatch custom event for other components
|
|
69
|
+
const event = new CustomEvent('tabChanged', {
|
|
70
|
+
detail: { tabId: targetId }
|
|
71
|
+
});
|
|
72
|
+
container.dispatchEvent(event);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Add keyboard navigation support
|
|
78
|
+
*/
|
|
79
|
+
function initKeyboardNavigation() {
|
|
80
|
+
document.addEventListener('keydown', function(e) {
|
|
81
|
+
const activeTab = document.querySelector('.tab-button.active');
|
|
82
|
+
|
|
83
|
+
if (!activeTab) return;
|
|
84
|
+
|
|
85
|
+
const container = activeTab.closest('.tabs-container');
|
|
86
|
+
const tabButtons = Array.from(container.querySelectorAll('.tab-button'));
|
|
87
|
+
const currentIndex = tabButtons.indexOf(activeTab);
|
|
88
|
+
|
|
89
|
+
let nextIndex;
|
|
90
|
+
|
|
91
|
+
// Arrow key navigation
|
|
92
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : tabButtons.length - 1;
|
|
95
|
+
} else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
nextIndex = currentIndex < tabButtons.length - 1 ? currentIndex + 1 : 0;
|
|
98
|
+
} else if (e.key === 'Home') {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
nextIndex = 0;
|
|
101
|
+
} else if (e.key === 'End') {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
nextIndex = tabButtons.length - 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (nextIndex !== undefined) {
|
|
107
|
+
const nextTab = tabButtons[nextIndex];
|
|
108
|
+
const targetId = nextTab.dataset.tab;
|
|
109
|
+
switchTab(container, targetId);
|
|
110
|
+
nextTab.focus();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Save/restore tab state in URL hash
|
|
117
|
+
*/
|
|
118
|
+
function initHashNavigation() {
|
|
119
|
+
// Restore tab from hash on load
|
|
120
|
+
if (window.location.hash) {
|
|
121
|
+
const tabId = window.location.hash.substring(1);
|
|
122
|
+
const tabButton = document.querySelector(`[data-tab="${tabId}"]`);
|
|
123
|
+
if (tabButton) {
|
|
124
|
+
const container = tabButton.closest('.tabs-container');
|
|
125
|
+
switchTab(container, tabId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Update hash when tab changes
|
|
130
|
+
document.addEventListener('tabChanged', function(e) {
|
|
131
|
+
const tabId = e.detail.tabId;
|
|
132
|
+
if (history.replaceState) {
|
|
133
|
+
history.replaceState(null, null, '#' + tabId);
|
|
134
|
+
} else {
|
|
135
|
+
window.location.hash = tabId;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Initialize everything when DOM is ready
|
|
142
|
+
*/
|
|
143
|
+
if (document.readyState === 'loading') {
|
|
144
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
145
|
+
initTabs();
|
|
146
|
+
initKeyboardNavigation();
|
|
147
|
+
initHashNavigation();
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
initTabs();
|
|
151
|
+
initKeyboardNavigation();
|
|
152
|
+
initHashNavigation();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Export functions for external use
|
|
156
|
+
window.PanelBoxTabs = {
|
|
157
|
+
init: initTabs,
|
|
158
|
+
switchTab: switchTab
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
})();
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PanelBox Reports - Utility Functions
|
|
3
|
+
* =====================================
|
|
4
|
+
*
|
|
5
|
+
* Common utility functions for reports.
|
|
6
|
+
*
|
|
7
|
+
* Version: 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
(function() {
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Format a number with specified decimal places
|
|
15
|
+
* @param {number} value - The number to format
|
|
16
|
+
* @param {number} decimals - Number of decimal places
|
|
17
|
+
* @returns {string} Formatted number
|
|
18
|
+
*/
|
|
19
|
+
function formatNumber(value, decimals = 3) {
|
|
20
|
+
if (value === null || value === undefined || isNaN(value)) {
|
|
21
|
+
return 'N/A';
|
|
22
|
+
}
|
|
23
|
+
return value.toFixed(decimals);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Format a p-value with scientific notation if small
|
|
28
|
+
* @param {number} pvalue - The p-value to format
|
|
29
|
+
* @returns {string} Formatted p-value
|
|
30
|
+
*/
|
|
31
|
+
function formatPValue(pvalue) {
|
|
32
|
+
if (pvalue === null || pvalue === undefined || isNaN(pvalue)) {
|
|
33
|
+
return 'N/A';
|
|
34
|
+
}
|
|
35
|
+
if (pvalue < 0.001) {
|
|
36
|
+
return pvalue.toExponential(2);
|
|
37
|
+
}
|
|
38
|
+
return pvalue.toFixed(4);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Format a percentage
|
|
43
|
+
* @param {number} value - The value to format as percentage
|
|
44
|
+
* @param {number} decimals - Number of decimal places
|
|
45
|
+
* @returns {string} Formatted percentage
|
|
46
|
+
*/
|
|
47
|
+
function formatPercentage(value, decimals = 2) {
|
|
48
|
+
if (value === null || value === undefined || isNaN(value)) {
|
|
49
|
+
return 'N/A';
|
|
50
|
+
}
|
|
51
|
+
return (value * 100).toFixed(decimals) + '%';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Add significance stars to a p-value
|
|
56
|
+
* @param {number} pvalue - The p-value
|
|
57
|
+
* @returns {string} Stars string
|
|
58
|
+
*/
|
|
59
|
+
function significanceStars(pvalue) {
|
|
60
|
+
if (pvalue < 0.001) return '***';
|
|
61
|
+
if (pvalue < 0.01) return '**';
|
|
62
|
+
if (pvalue < 0.05) return '*';
|
|
63
|
+
if (pvalue < 0.1) return '.';
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Copy text to clipboard
|
|
69
|
+
* @param {string} text - Text to copy
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async function copyToClipboard(text) {
|
|
73
|
+
try {
|
|
74
|
+
await navigator.clipboard.writeText(text);
|
|
75
|
+
showNotification('Copied to clipboard!', 'success');
|
|
76
|
+
} catch (err) {
|
|
77
|
+
// Fallback for older browsers
|
|
78
|
+
const textarea = document.createElement('textarea');
|
|
79
|
+
textarea.value = text;
|
|
80
|
+
textarea.style.position = 'fixed';
|
|
81
|
+
textarea.style.opacity = '0';
|
|
82
|
+
document.body.appendChild(textarea);
|
|
83
|
+
textarea.select();
|
|
84
|
+
try {
|
|
85
|
+
document.execCommand('copy');
|
|
86
|
+
showNotification('Copied to clipboard!', 'success');
|
|
87
|
+
} catch (err2) {
|
|
88
|
+
showNotification('Failed to copy', 'danger');
|
|
89
|
+
}
|
|
90
|
+
document.body.removeChild(textarea);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Show a temporary notification
|
|
96
|
+
* @param {string} message - Message to show
|
|
97
|
+
* @param {string} type - Type: success, warning, danger, info
|
|
98
|
+
* @param {number} duration - Duration in ms
|
|
99
|
+
*/
|
|
100
|
+
function showNotification(message, type = 'info', duration = 3000) {
|
|
101
|
+
const notification = document.createElement('div');
|
|
102
|
+
notification.className = `notification notification-${type}`;
|
|
103
|
+
notification.textContent = message;
|
|
104
|
+
notification.style.cssText = `
|
|
105
|
+
position: fixed;
|
|
106
|
+
top: 20px;
|
|
107
|
+
right: 20px;
|
|
108
|
+
padding: 12px 24px;
|
|
109
|
+
border-radius: 8px;
|
|
110
|
+
background: white;
|
|
111
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
112
|
+
z-index: 9999;
|
|
113
|
+
animation: slideInRight 0.3s ease;
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
// Set color based on type
|
|
117
|
+
const colors = {
|
|
118
|
+
success: '#10b981',
|
|
119
|
+
warning: '#f59e0b',
|
|
120
|
+
danger: '#ef4444',
|
|
121
|
+
info: '#3b82f6'
|
|
122
|
+
};
|
|
123
|
+
notification.style.borderLeft = `4px solid ${colors[type] || colors.info}`;
|
|
124
|
+
|
|
125
|
+
document.body.appendChild(notification);
|
|
126
|
+
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
notification.style.animation = 'slideOutRight 0.3s ease';
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
document.body.removeChild(notification);
|
|
131
|
+
}, 300);
|
|
132
|
+
}, duration);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Export table to CSV
|
|
137
|
+
* @param {string} tableId - ID of the table element
|
|
138
|
+
* @param {string} filename - Name for the CSV file
|
|
139
|
+
*/
|
|
140
|
+
function exportTableToCSV(tableId, filename = 'table.csv') {
|
|
141
|
+
const table = document.getElementById(tableId);
|
|
142
|
+
if (!table) return;
|
|
143
|
+
|
|
144
|
+
const rows = Array.from(table.querySelectorAll('tr'));
|
|
145
|
+
const csv = rows.map(row => {
|
|
146
|
+
const cells = Array.from(row.querySelectorAll('th, td'));
|
|
147
|
+
return cells.map(cell => {
|
|
148
|
+
let text = cell.textContent.trim();
|
|
149
|
+
// Escape quotes and wrap in quotes if contains comma
|
|
150
|
+
if (text.includes(',') || text.includes('"')) {
|
|
151
|
+
text = '"' + text.replace(/"/g, '""') + '"';
|
|
152
|
+
}
|
|
153
|
+
return text;
|
|
154
|
+
}).join(',');
|
|
155
|
+
}).join('\n');
|
|
156
|
+
|
|
157
|
+
// Download CSV
|
|
158
|
+
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
159
|
+
const link = document.createElement('a');
|
|
160
|
+
const url = URL.createObjectURL(blob);
|
|
161
|
+
link.setAttribute('href', url);
|
|
162
|
+
link.setAttribute('download', filename);
|
|
163
|
+
link.style.visibility = 'hidden';
|
|
164
|
+
document.body.appendChild(link);
|
|
165
|
+
link.click();
|
|
166
|
+
document.body.removeChild(link);
|
|
167
|
+
URL.revokeObjectURL(url);
|
|
168
|
+
|
|
169
|
+
showNotification('Table exported to CSV', 'success');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Print the report
|
|
174
|
+
*/
|
|
175
|
+
function printReport() {
|
|
176
|
+
window.print();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Toggle element visibility
|
|
181
|
+
* @param {string} elementId - ID of element to toggle
|
|
182
|
+
*/
|
|
183
|
+
function toggleVisibility(elementId) {
|
|
184
|
+
const element = document.getElementById(elementId);
|
|
185
|
+
if (element) {
|
|
186
|
+
element.style.display = element.style.display === 'none' ? '' : 'none';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Smooth scroll to element
|
|
192
|
+
* @param {string} elementId - ID of element to scroll to
|
|
193
|
+
*/
|
|
194
|
+
function scrollToElement(elementId) {
|
|
195
|
+
const element = document.getElementById(elementId);
|
|
196
|
+
if (element) {
|
|
197
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Debounce function
|
|
203
|
+
* @param {Function} func - Function to debounce
|
|
204
|
+
* @param {number} wait - Wait time in ms
|
|
205
|
+
* @returns {Function} Debounced function
|
|
206
|
+
*/
|
|
207
|
+
function debounce(func, wait = 300) {
|
|
208
|
+
let timeout;
|
|
209
|
+
return function executedFunction(...args) {
|
|
210
|
+
const later = () => {
|
|
211
|
+
clearTimeout(timeout);
|
|
212
|
+
func(...args);
|
|
213
|
+
};
|
|
214
|
+
clearTimeout(timeout);
|
|
215
|
+
timeout = setTimeout(later, wait);
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Check if element is in viewport
|
|
221
|
+
* @param {HTMLElement} element - Element to check
|
|
222
|
+
* @returns {boolean} True if in viewport
|
|
223
|
+
*/
|
|
224
|
+
function isInViewport(element) {
|
|
225
|
+
const rect = element.getBoundingClientRect();
|
|
226
|
+
return (
|
|
227
|
+
rect.top >= 0 &&
|
|
228
|
+
rect.left >= 0 &&
|
|
229
|
+
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
230
|
+
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Add CSS for animations
|
|
235
|
+
const style = document.createElement('style');
|
|
236
|
+
style.textContent = `
|
|
237
|
+
@keyframes slideInRight {
|
|
238
|
+
from {
|
|
239
|
+
transform: translateX(100%);
|
|
240
|
+
opacity: 0;
|
|
241
|
+
}
|
|
242
|
+
to {
|
|
243
|
+
transform: translateX(0);
|
|
244
|
+
opacity: 1;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
@keyframes slideOutRight {
|
|
248
|
+
from {
|
|
249
|
+
transform: translateX(0);
|
|
250
|
+
opacity: 1;
|
|
251
|
+
}
|
|
252
|
+
to {
|
|
253
|
+
transform: translateX(100%);
|
|
254
|
+
opacity: 0;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
`;
|
|
258
|
+
document.head.appendChild(style);
|
|
259
|
+
|
|
260
|
+
// Export utilities
|
|
261
|
+
window.PanelBoxUtils = {
|
|
262
|
+
formatNumber,
|
|
263
|
+
formatPValue,
|
|
264
|
+
formatPercentage,
|
|
265
|
+
significanceStars,
|
|
266
|
+
copyToClipboard,
|
|
267
|
+
showNotification,
|
|
268
|
+
exportTableToCSV,
|
|
269
|
+
printReport,
|
|
270
|
+
toggleVisibility,
|
|
271
|
+
scrollToElement,
|
|
272
|
+
debounce,
|
|
273
|
+
isInViewport
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
})();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<div class="report-footer">
|
|
2
|
+
<div class="report-footer-actions no-print">
|
|
3
|
+
<button class="btn btn-secondary btn-sm" onclick="window.print()">
|
|
4
|
+
Print Report
|
|
5
|
+
</button>
|
|
6
|
+
{% if show_export_buttons %}
|
|
7
|
+
<button class="btn btn-secondary btn-sm" onclick="PanelBoxUtils.exportTableToCSV('summary-table', 'panelbox_results.csv')">
|
|
8
|
+
Export to CSV
|
|
9
|
+
</button>
|
|
10
|
+
{% endif %}
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="report-footer-text">
|
|
14
|
+
<p>
|
|
15
|
+
Generated with <strong>PanelBox {{ panelbox_version }}</strong> |
|
|
16
|
+
<a href="https://github.com/panelbox/panelbox" target="_blank">Documentation</a>
|
|
17
|
+
</p>
|
|
18
|
+
<p class="text-xs text-gray-500">
|
|
19
|
+
Report Type: {{ report_type }} |
|
|
20
|
+
Generated: {{ generation_date }} |
|
|
21
|
+
Python {{ python_version }}
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<div class="report-header">
|
|
2
|
+
<h1 class="report-title">{{ report_title }}</h1>
|
|
3
|
+
{% if report_subtitle %}
|
|
4
|
+
<p class="report-subtitle">{{ report_subtitle }}</p>
|
|
5
|
+
{% endif %}
|
|
6
|
+
|
|
7
|
+
<div class="report-meta">
|
|
8
|
+
<div class="report-meta-item">
|
|
9
|
+
<span class="report-meta-label">Model:</span>
|
|
10
|
+
<span>{{ model_type }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
{% if formula %}
|
|
14
|
+
<div class="report-meta-item">
|
|
15
|
+
<span class="report-meta-label">Formula:</span>
|
|
16
|
+
<code class="text-sm">{{ formula }}</code>
|
|
17
|
+
</div>
|
|
18
|
+
{% endif %}
|
|
19
|
+
|
|
20
|
+
<div class="report-meta-item">
|
|
21
|
+
<span class="report-meta-label">Observations:</span>
|
|
22
|
+
<span>{{ nobs|number_format }}</span>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
{% if n_entities %}
|
|
26
|
+
<div class="report-meta-item">
|
|
27
|
+
<span class="report-meta-label">Entities:</span>
|
|
28
|
+
<span>{{ n_entities|number_format }}</span>
|
|
29
|
+
</div>
|
|
30
|
+
{% endif %}
|
|
31
|
+
|
|
32
|
+
{% if n_periods %}
|
|
33
|
+
<div class="report-meta-item">
|
|
34
|
+
<span class="report-meta-label">Periods:</span>
|
|
35
|
+
<span>{{ n_periods }}</span>
|
|
36
|
+
</div>
|
|
37
|
+
{% endif %}
|
|
38
|
+
|
|
39
|
+
<div class="report-meta-item">
|
|
40
|
+
<span class="report-meta-label">Generated:</span>
|
|
41
|
+
<span>{{ generation_date }}</span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<meta charset="UTF-8">
|
|
2
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3
|
+
<meta name="generator" content="PanelBox {{ panelbox_version }}">
|
|
4
|
+
<meta name="description" content="PanelBox {{ report_type }} Report{% if report_title %} - {{ report_title }}{% endif %}">
|
|
5
|
+
<title>{% if report_title %}{{ report_title }} - {% endif %}PanelBox Report</title>
|