modelflowib 2.73__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.
- modelBLfunk.py +180 -0
- model_Excel.py +332 -0
- model_cvx.py +139 -0
- model_dynare.py +173 -0
- model_financial_stability.py +88 -0
- model_latex.py +497 -0
- model_latex_class.py +808 -0
- model_parquet_mixin.py +424 -0
- modelclass.py +9828 -0
- modelconstruct.py +1496 -0
- modelconstruct_estimation.py +2872 -0
- modeldash.py +265 -0
- modeldashboot.py +202 -0
- modeldashsidebar.py +456 -0
- modeldekom.py +651 -0
- modeldiff.py +561 -0
- modeldisplay.py +550 -0
- modelestimation.py +1776 -0
- modelestimator_new.py +2613 -0
- modelflowib-2.73.dist-info/METADATA +156 -0
- modelflowib-2.73.dist-info/RECORD +44 -0
- modelflowib-2.73.dist-info/WHEEL +5 -0
- modelflowib-2.73.dist-info/licenses/license.md +10 -0
- modelflowib-2.73.dist-info/top_level.txt +39 -0
- modelgrab.py +318 -0
- modelgrabgdx.py +584 -0
- modelgrabwf2.py +1107 -0
- modelhelp.py +543 -0
- modelhtml.py +606 -0
- modelinvert.py +250 -0
- modeljupyter.py +824 -0
- modeljupytermagic.py +813 -0
- modelmacrograb.py +98 -0
- modelmanipulation.py +1461 -0
- modelmf.py +349 -0
- modelnet.py +114 -0
- modelnewton.py +2178 -0
- modelnormalize.py +430 -0
- modelpattern.py +428 -0
- modelreport.py +2187 -0
- modeluserfunk.py +97 -0
- modelvis.py +1038 -0
- modelwidget.py +718 -0
- modelwidget_input.py +1933 -0
modelreport.py
ADDED
|
@@ -0,0 +1,2187 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Mon Apr 8 14:58:26 2024
|
|
4
|
+
|
|
5
|
+
@author: ibhan
|
|
6
|
+
|
|
7
|
+
The `modelreport` module facilitates the generation, management, and display of data visualizations and tables
|
|
8
|
+
derived from ModelFlow models. It is designed to support a wide range of output formats including LaTeX documents,
|
|
9
|
+
HTML, and interactive IPyWidgets, making it highly versatile for different reporting and analysis needs in Jupyter
|
|
10
|
+
notebook environments.
|
|
11
|
+
|
|
12
|
+
This module provides a structured and extendible approach to organizing and rendering data analysis results, enabling users to
|
|
13
|
+
easily convert complex model outputs into digestible visual representations and tables. It is especially useful
|
|
14
|
+
for automating the reporting process in data analysis, financial modeling, and research projects, leveraging Python's
|
|
15
|
+
capabilities for data processing and visualization along with advanced document presentation features.
|
|
16
|
+
|
|
17
|
+
Key Features:
|
|
18
|
+
|
|
19
|
+
- Dynamic generation of LaTeX and HTML content for integrating data visualizations and tables into reports and
|
|
20
|
+
presentations.
|
|
21
|
+
- Compatibility with IPyWidgets for creating interactive, widget-based displays that enhance the interactivity of
|
|
22
|
+
Jupyter notebooks.
|
|
23
|
+
- Seamless integration with matplotlib for figure generation and pandas for table formatting, providing a comprehensive
|
|
24
|
+
toolkit for data display.
|
|
25
|
+
- Customizable display options and specifications through dataclasses, allowing for tailored presentation styles
|
|
26
|
+
and formats.
|
|
27
|
+
|
|
28
|
+
Classes:
|
|
29
|
+
|
|
30
|
+
- :class:`Options`: Configures display options for managing how data and figures are presented, including naming conventions,
|
|
31
|
+
formatting preferences, and title settings.
|
|
32
|
+
- :class:`Line`: Defines line configurations for table displays, supporting various data representation and difference
|
|
33
|
+
calculations to suit different analysis needs.
|
|
34
|
+
- :class:`DisplaySpec`: Groups display options and line configurations, facilitating the management of complex display setups
|
|
35
|
+
in a structured manner.
|
|
36
|
+
- :class:`DisplayDef`: Base class for display definitions, capable of compiling various display components into cohesive
|
|
37
|
+
specifications for rendering.
|
|
38
|
+
- :class:`LatexRepo`: Handles the generation of LaTeX content, compilation into PDFs, and embedding within Jupyter notebooks,
|
|
39
|
+
supporting both static and dynamic content creation.
|
|
40
|
+
- :class:`DisplayVarTableDef`: Specializes in displaying variable tables, automating the creation and formatting of tables
|
|
41
|
+
from ModelFlow model outputs.
|
|
42
|
+
- :class:`DisplayFigWrapDef`: Focuses on wrapping and adjusting matplotlib figures for inclusion in various display formats,
|
|
43
|
+
ensuring figures are presentation-ready.
|
|
44
|
+
- :class:`SplitTextResult`: Parses a string containing text with embedded <html>, <latex>, and <markdown> tags and separates
|
|
45
|
+
the content accordingly.
|
|
46
|
+
- :class:`DatatypeAccessor`: Manages configurations for different datatypes, allowing for easy access and parsing of
|
|
47
|
+
configuration tables provided in Markdown format.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
The `modeldisplay` module bridges the gap between analytical modeling and result presentation, offering a streamlined
|
|
51
|
+
workflow for transforming ModelFlow model outputs into high-quality visual and tabular displays suitable for a wide
|
|
52
|
+
range of purposes.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
import pandas as pd
|
|
60
|
+
import matplotlib.pyplot as plt
|
|
61
|
+
import matplotlib as mpl
|
|
62
|
+
import seaborn as sns
|
|
63
|
+
import fnmatch
|
|
64
|
+
import re
|
|
65
|
+
from matplotlib import dates
|
|
66
|
+
import matplotlib.ticker as ticker
|
|
67
|
+
import matplotlib.gridspec as gridspec
|
|
68
|
+
from matplotlib.figure import Figure
|
|
69
|
+
import numpy as np
|
|
70
|
+
|
|
71
|
+
from dataclasses import dataclass, field, fields, asdict
|
|
72
|
+
from typing import Any, List, Dict, Optional , Tuple
|
|
73
|
+
from copy import deepcopy
|
|
74
|
+
import json
|
|
75
|
+
import ipywidgets as widgets
|
|
76
|
+
from io import StringIO
|
|
77
|
+
|
|
78
|
+
from IPython.display import display, clear_output,Latex, Markdown,HTML , IFrame
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
from subprocess import run
|
|
85
|
+
from pathlib import Path
|
|
86
|
+
import webbrowser as wb
|
|
87
|
+
|
|
88
|
+
WIDTH = '100%'
|
|
89
|
+
HEIGHT = '400px'
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
from dataclasses import dataclass, field, fields, MISSING
|
|
93
|
+
from typing import Dict, List
|
|
94
|
+
from copy import deepcopy
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
from modelwidget import fig_to_image,tabwidget,htmlwidget_fig, htmlwidget_df,htmlwidget_text,htmlwidget_style
|
|
98
|
+
|
|
99
|
+
def track_fields():
|
|
100
|
+
"""
|
|
101
|
+
A decorator to track which fields in a dataclass have been explicitly set during initialization.
|
|
102
|
+
|
|
103
|
+
This decorator modifies the `__init__` method of a dataclass to keep track of which fields
|
|
104
|
+
have been explicitly set by the user when an instance is created. The explicitly set fields
|
|
105
|
+
are stored in a set called `__explicitly_set__`.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
wrap (function): A decorator function that modifies the dataclass to track explicitly set fields.
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
def wrap(cls):
|
|
112
|
+
cls.__original_init = cls.__init__
|
|
113
|
+
def __init__(self, *args, **kw):
|
|
114
|
+
kw.update(dict(zip(cls.__dataclass_fields__.keys(), args)))
|
|
115
|
+
self.__explicitly_set__ = set(kw.keys())
|
|
116
|
+
kw = {k: v for k, v in kw.items() if k in cls.__dataclass_fields__}
|
|
117
|
+
self.__original_init(**kw)
|
|
118
|
+
cls.__init__ = __init__
|
|
119
|
+
return cls
|
|
120
|
+
return wrap
|
|
121
|
+
|
|
122
|
+
class DatatypeAccessor:
|
|
123
|
+
def __init__(self, datatype, **kwargs):
|
|
124
|
+
"""
|
|
125
|
+
Initializes the ConfigAccessor with a datatype and a configuration table in Markdown format.
|
|
126
|
+
|
|
127
|
+
:param datatype: A string keyword to fetch configuration for.
|
|
128
|
+
:param config_table: A string representing the configuration in Markdown table format.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
self.datatype = datatype
|
|
132
|
+
|
|
133
|
+
# {var_name}
|
|
134
|
+
# {var_description}
|
|
135
|
+
# {compare} Keep name used for comparison
|
|
136
|
+
|
|
137
|
+
if 'config_table' in kwargs:
|
|
138
|
+
config_table = kwargs.get('config_table')
|
|
139
|
+
else:
|
|
140
|
+
|
|
141
|
+
config_table = r"""
|
|
142
|
+
| datatype | showtype | diftype | col_desc | ax_title_template|
|
|
143
|
+
|-----------|----------|---------|------------------|------------|
|
|
144
|
+
| growth | growth | nodif | Percent growth | {var_description} \n% growth |
|
|
145
|
+
| difgrowth | growth | dif | Impact, Percent growth | {var_description} \nImpact, % growth v|
|
|
146
|
+
| gdppct | gdppct | nodif | Percent of GDP | {var_description} \n(% GDP) |
|
|
147
|
+
| difgdppct | gdppct | dif | Impact, Percent of GDP | {var_description} \nImpact (% GDP) vs {compare} |
|
|
148
|
+
| level | level | nodif | Level | {var_description} |
|
|
149
|
+
| diflevel | level | dif | Impact, Level | {var_description} \nChange vs {compare} |
|
|
150
|
+
| difpctlevel| level | difpct | Impact in percent | {var_description} \n% Change vs {compare} |
|
|
151
|
+
| qoq_ar | qoq_ar | nodif | Q-Q anuallized | {var_description} \nQ-Q annualized |
|
|
152
|
+
| difqoq_ar | qoq_ar | dif | Impact Q-Q anuallized | {var_description} \nImpact Q-Q annualized vs {compare} |
|
|
153
|
+
| baselevel | level | basedf | Base Level | {var_description} \nBase Level |
|
|
154
|
+
| basegrowth | growth | basedf | Base Percent growth | {var_description} \nBase % growth |
|
|
155
|
+
| basegdppct | gdppct | basedf | Base Percent of GDP | {var_description} \nBase % of GDP |
|
|
156
|
+
| baseqoq_ar | qoq_ar | basedf | Base Q-Q anuallized | {var_description} \nBase Q-Q annualized |
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
config_table = r"""
|
|
162
|
+
| datatype | showtype | diftype | col_desc | ax_title_template | ax_title_template_df |
|
|
163
|
+
|--------------|-----------|---------|---------------------------|-------------------------------------------------|------------------------------------------------|
|
|
164
|
+
| growth | growth | nodif | Percent growth | {var_description} \n% growth | {var_description} \n% growth |
|
|
165
|
+
| difgrowth | growth | dif | Impact, Percent growth | {var_description} \nImpact, % growth vs {compare} | {var_description} \nImpact, % |
|
|
166
|
+
| gdppct | gdppct | nodif | Percent of GDP | {var_description} \n(% GDP) | {var_description} \n(% GDP) |
|
|
167
|
+
| difgdppct | gdppct | dif | Impact, Percent of GDP | {var_description} \nImpact (% GDP) vs {compare} | {var_description} \nImpact (% GDP) |
|
|
168
|
+
| level | level | nodif | Level | {var_description} | {var_description} |
|
|
169
|
+
| diflevel | level | dif | Impact, Level | {var_description} \nChange vs {compare} | {var_description} \nChange |
|
|
170
|
+
| difpctlevel | level | difpct | Impact in percent | {var_description} \n% Change vs {compare} | {var_description} \n% Change |
|
|
171
|
+
| qoq_ar | qoq_ar | nodif | Q-Q annualized | {var_description} \nQ-Q annualized | {var_description} \nQ-Q annualized |
|
|
172
|
+
| difqoq_ar | qoq_ar | dif | Impact Q-Q annualized | {var_description} \nImpact Q-Q annualized vs {compare} | {var_description} \nImpact Q-Q annualized |
|
|
173
|
+
| baselevel | level | basedf | Baseline | {var_description} \nBaseline | {var_description} \nBaseline Level |
|
|
174
|
+
| basegrowth | growth | basedf | Baseline Percent growth | {var_description} \nBaseline % growth | {var_description} \nBaseline % growth |
|
|
175
|
+
| basegdppct | gdppct | basedf | Baseline Percent of GDP | {var_description} \nBaseline % of GDP | {var_description} \nBaseline % of GDP |
|
|
176
|
+
| baseqoq_ar | qoq_ar | basedf | Baseline Q-Q annualized | {var_description} \nBaseline Q-Q annualized | {var_description} \\nBaseline Q-Q annualized |
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
self.configurations = self.parse_config_table(config_table)
|
|
182
|
+
# print(self.configurations)
|
|
183
|
+
|
|
184
|
+
# Apply any overrides from kwargs
|
|
185
|
+
# if datatype in self.configurations:
|
|
186
|
+
# self.configurations[datatype].update((k, v) for k, v in kwargs.items() if k in self.configurations[datatype])
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def parse_config_table(self,config_table):
|
|
190
|
+
"""
|
|
191
|
+
Parses a Markdown table into a dictionary of configurations.
|
|
192
|
+
|
|
193
|
+
:param config_table: Markdown table as a string.
|
|
194
|
+
:return: Dictionary with datatype keys and property dictionaries as values.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
lines = config_table.strip().split('\n')
|
|
198
|
+
headers = re.split(r'\s*\|\s*', lines[0].strip('|').strip())
|
|
199
|
+
configs = {}
|
|
200
|
+
|
|
201
|
+
for line in lines[2:]: # Skip the header and delimiter rows
|
|
202
|
+
values = re.split(r'\s*\|\s*', line.strip('|').strip())
|
|
203
|
+
# print(f'{line=}')
|
|
204
|
+
# print(f'{values=}')
|
|
205
|
+
config = {headers[i]: values[i] for i in range(len(values))}
|
|
206
|
+
datatype = config.pop('datatype') # Remove the datatype key to use as the dictionary key
|
|
207
|
+
configs[datatype] = config
|
|
208
|
+
|
|
209
|
+
return configs
|
|
210
|
+
|
|
211
|
+
def __getattr__(self, item):
|
|
212
|
+
"""
|
|
213
|
+
Provides dynamic access to configuration properties based on the initial datatype.
|
|
214
|
+
|
|
215
|
+
:param item: The property name to fetch from the configuration for the provided datatype.
|
|
216
|
+
:return: The value associated with 'item' under the specified datatype's configuration.
|
|
217
|
+
"""
|
|
218
|
+
config_data = self.configurations.get(self.datatype)
|
|
219
|
+
|
|
220
|
+
if not config_data:
|
|
221
|
+
allowed = '\nAllowed datatypes:'+'\n' + '\n'.join(self.configurations.keys() )
|
|
222
|
+
raise ValueError(f"Configuration for datatype '{self.datatype}' not found"+allowed)
|
|
223
|
+
|
|
224
|
+
return config_data.get(item, '')
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@track_fields()
|
|
234
|
+
@dataclass
|
|
235
|
+
class Options:
|
|
236
|
+
"""
|
|
237
|
+
Represents configuration options for data display definitions.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
name (str): Name for this display. Default is 'Display'.
|
|
241
|
+
foot (str): Footer if relevant. Default is an empty string.
|
|
242
|
+
rename (bool): If True, allows renaming of data columns. Default is True.
|
|
243
|
+
decorate (bool): If True, decorates row descriptions based on the showtype. Default is True.
|
|
244
|
+
width (int): Specifies the width for formatting output in characters. Default is 20.
|
|
245
|
+
custom_description (Dict[str, str]): Custom descriptions to augment or override default descriptions. Empty by default.
|
|
246
|
+
title (str): Text for the title. Default is an empty string.
|
|
247
|
+
chunk_size (int): Specifies the number of columns per chunk in the display output. Default is 0 (no chunking).
|
|
248
|
+
timeslice (List): Specifies the time slice for data display. Empty by default.
|
|
249
|
+
max_cols (int): Maximum columns when displayed as string. Default is 6.
|
|
250
|
+
last_cols (int): In Latex, specifies the number of last columns to include in a display slice. Default is 1.
|
|
251
|
+
ncol (int): Number of columns in figures. Default is 2.
|
|
252
|
+
samefig (bool): If True, use the same figure for multiple plots. Default is False.
|
|
253
|
+
size (tuple): Tuple specifying the figure size (width, height). Default is (10, 6).
|
|
254
|
+
legend (bool): If True, display legend in plots. Default is True.
|
|
255
|
+
transpose (bool): If True, transpose the data when displaying. Default is False.
|
|
256
|
+
scenarios (str): Text specifying the scenarios for the display. Default is an empty string. if Empty use basedf/lastdf
|
|
257
|
+
smpl (tuple): Tuple specifying start and end periods. Default is ('', '').
|
|
258
|
+
landscape (bool): If True, the table will be displayed in landscape mode. Default is False.
|
|
259
|
+
latex_text (str): Text for a LaTeX output. Default is an empty string.
|
|
260
|
+
html_text (str): Text for an HTML output. Default is an empty string.
|
|
261
|
+
text_text (str): Text for a plain text output. Default is an empty string.
|
|
262
|
+
markdown_text (str): Text for a Markdown output. Default is an empty string.
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
Methods:
|
|
266
|
+
__add__(other): Merges this Options instance with another 'Options' instance or a dictionary. It returns a new Options
|
|
267
|
+
instance that combines settings from both, preferring non-default values from 'other'. If 'other' is a
|
|
268
|
+
dictionary, attributes not existing in this instance will raise an AttributeError. TypeErrors are raised
|
|
269
|
+
when 'other' is neither a dictionary nor an Options instance.
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
"""
|
|
273
|
+
name: str = 'Display'
|
|
274
|
+
foot: str = ''
|
|
275
|
+
rename: bool = True
|
|
276
|
+
decorate: bool = True
|
|
277
|
+
width: int = 20
|
|
278
|
+
custom_description: Dict[str, str] = field(default_factory=dict)
|
|
279
|
+
title: str = ''
|
|
280
|
+
chunk_size: int = 0
|
|
281
|
+
timeslice: List = field(default_factory=list)
|
|
282
|
+
max_cols: int = 6
|
|
283
|
+
last_cols: int = 3
|
|
284
|
+
|
|
285
|
+
ncol : int = 2
|
|
286
|
+
samefig :bool = False
|
|
287
|
+
size: tuple = (10, 6)
|
|
288
|
+
legend: bool = True
|
|
289
|
+
transpose : bool = False
|
|
290
|
+
scenarios : str =''
|
|
291
|
+
smpl : tuple = ('','')
|
|
292
|
+
landscape : bool = False
|
|
293
|
+
|
|
294
|
+
latex_text :str =''
|
|
295
|
+
html_text :str =''
|
|
296
|
+
text_text :str =''
|
|
297
|
+
markdown_text :str =''
|
|
298
|
+
|
|
299
|
+
def __post_init__(self):
|
|
300
|
+
|
|
301
|
+
if ' ' in self.name:
|
|
302
|
+
self.name = self.name.replace(' ','_')
|
|
303
|
+
# print(f'Blank space is not allowed in name, renamed: {self.name}')
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def was_explicitly_set(self, field_name: str) -> bool:
|
|
307
|
+
# Using getattr with three arguments to avoid KeyError and return False when the attribute isn't found
|
|
308
|
+
return getattr(self, '__explicitly_set__', set()).__contains__(field_name)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def __add__(self, other):
|
|
312
|
+
if not isinstance(other, (Options, dict)):
|
|
313
|
+
raise TypeError("Operand must be an instance of Options or dict.")
|
|
314
|
+
|
|
315
|
+
# Create a new instance by deeply copying the current one
|
|
316
|
+
new_instance = deepcopy(self)
|
|
317
|
+
|
|
318
|
+
# Get default values for comparison
|
|
319
|
+
default_values = {f.name: f.default if f.default is not MISSING else f.default_factory()
|
|
320
|
+
for f in fields(self)}
|
|
321
|
+
|
|
322
|
+
def is_explicitly_set(attr, value, default):
|
|
323
|
+
return value != default or attr in other # Check if the attribute is present in 'other'
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
if isinstance(other, dict):
|
|
327
|
+
# Update using dictionary
|
|
328
|
+
for key, value in other.items():
|
|
329
|
+
if hasattr(new_instance, key):
|
|
330
|
+
setattr(new_instance, key, value)
|
|
331
|
+
else:
|
|
332
|
+
...
|
|
333
|
+
# raise AttributeError(f"No such attribute {key} in Options class.")
|
|
334
|
+
elif isinstance(other, Options):
|
|
335
|
+
# Update using another Options instance, but only for non-default values
|
|
336
|
+
for key, value in vars(other).items():
|
|
337
|
+
if other.was_explicitly_set(key):
|
|
338
|
+
setattr(new_instance, key, value)
|
|
339
|
+
|
|
340
|
+
return new_instance
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@dataclass
|
|
347
|
+
class Line:
|
|
348
|
+
"""
|
|
349
|
+
A dataclass for representing and validating line configurations for data display.
|
|
350
|
+
|
|
351
|
+
Attributes:
|
|
352
|
+
datatype (str): Specifies the datatype as defines in DatatypeAccessor
|
|
353
|
+
textlinetype (str): = 'textline' if the line is a textline in a table
|
|
354
|
+
centertext (str): Center text used when showtype is 'textline'. Default is a space.
|
|
355
|
+
rename (bool): If True, allows renaming of data columns. Default is True.
|
|
356
|
+
dec (int): Specifies the number of decimal places to use for numerical output. Default is 2.
|
|
357
|
+
pat (str): Pattern or identifier used to select data for the line. Default is '#Headline'.
|
|
358
|
+
latexfont (str) : Modifier used in lates for instande r'\textbf'
|
|
359
|
+
default_ax_title_template(str) Table specific template for individual chart titles
|
|
360
|
+
ax_title_template(str) user provided Table specific template for individual chart titles
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
datatype :str = 'level'
|
|
364
|
+
scale : str = 'linear'
|
|
365
|
+
kind : str = 'line'
|
|
366
|
+
centertext : str = ''
|
|
367
|
+
rename: bool = False
|
|
368
|
+
dec: int = 2
|
|
369
|
+
pat : str = '#Headline'
|
|
370
|
+
latexfont :str =''
|
|
371
|
+
by_var :bool = True
|
|
372
|
+
mul : float = 1.0
|
|
373
|
+
yunit : str = ''
|
|
374
|
+
datatype_desc :str = ''
|
|
375
|
+
ax_title_template :str = ''
|
|
376
|
+
textlinetype :str = ''
|
|
377
|
+
|
|
378
|
+
# default_ax_title_template :str = field(init=False)
|
|
379
|
+
# default_ax_title_template_df :str = field(init=False)
|
|
380
|
+
# showtype: str = field(init=False)
|
|
381
|
+
# diftype: str = field(init=False)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def __post_init__(self):
|
|
385
|
+
|
|
386
|
+
config = DatatypeAccessor(self.datatype)
|
|
387
|
+
self.showtype = config.showtype
|
|
388
|
+
self.diftype = config.diftype
|
|
389
|
+
self.default_ax_title_template = config.ax_title_template
|
|
390
|
+
self.default_ax_title_template_df = config.ax_title_template_df
|
|
391
|
+
|
|
392
|
+
valid_showtypes = {'level', 'growth', 'change', 'basedf', 'gdppct' ,'textline','qoq_ar'}
|
|
393
|
+
valid_diftypes = {'nodif', 'dif', 'difpct', 'basedf', 'lastdf'}
|
|
394
|
+
|
|
395
|
+
if self.showtype not in valid_showtypes:
|
|
396
|
+
raise ValueError(f"showtype must be one of {valid_showtypes}, got {self.showtype}")
|
|
397
|
+
|
|
398
|
+
if self.diftype not in valid_diftypes:
|
|
399
|
+
raise ValueError(f"diftype must be one of {valid_diftypes}, got {self.diftype}")
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@dataclass
|
|
403
|
+
class DisplaySpec:
|
|
404
|
+
"""
|
|
405
|
+
A dataclass to encapsulate display specifications including options and a list of line configurations.
|
|
406
|
+
|
|
407
|
+
Attributes:
|
|
408
|
+
options (Options): An instance of the Options dataclass specifying configuration options.
|
|
409
|
+
lines (List[Line]): A list of Line instances specifying individual line configurations.
|
|
410
|
+
"""
|
|
411
|
+
display_type : str = ''
|
|
412
|
+
options: Options = field(default_factory=Options)
|
|
413
|
+
lines: List[Line] = field(default_factory=list)
|
|
414
|
+
|
|
415
|
+
def __add__(self, other):
|
|
416
|
+
|
|
417
|
+
if isinstance(other, DisplaySpec):
|
|
418
|
+
new_options = self.options + other.options
|
|
419
|
+
new_lines = self.lines + other.lines # extends the list with other's lines
|
|
420
|
+
elif isinstance(other, Options):
|
|
421
|
+
new_options = self.options + other # update options
|
|
422
|
+
new_lines = self.lines
|
|
423
|
+
elif isinstance(other, dict):
|
|
424
|
+
new_options = self.options + other # update options
|
|
425
|
+
new_lines = self.lines
|
|
426
|
+
elif isinstance(other, Line):
|
|
427
|
+
new_lines = self.lines + [other] # creates a new list with the added line
|
|
428
|
+
elif isinstance(other, list) and all(isinstance(line, Line) for line in other):
|
|
429
|
+
new_lines = self.lines + other # extends the list with the new lines
|
|
430
|
+
else:
|
|
431
|
+
raise TypeError("Operand must be an instance of DisplaySpec, Options, dict, Line, or list of Line instances.")
|
|
432
|
+
|
|
433
|
+
return DisplaySpec(options=new_options, lines=new_lines)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@classmethod
|
|
437
|
+
def from_json(cls, json_str: str) -> 'DisplaySpec':
|
|
438
|
+
"""
|
|
439
|
+
Creates a DisplaySpec instance from a JSON string using the class that called this method,
|
|
440
|
+
which supports use by subclasses.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
json_str (str): A JSON string representation of a DisplaySpec instance.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
DisplaySpec: The constructed DisplaySpec instance (or an instance of a subclass).
|
|
447
|
+
"""
|
|
448
|
+
data = json.loads(json_str)
|
|
449
|
+
options_data = data['options']
|
|
450
|
+
lines_data = data['lines']
|
|
451
|
+
|
|
452
|
+
# Create Options instance from options data
|
|
453
|
+
options = Options(**options_data)
|
|
454
|
+
|
|
455
|
+
# Create list of Line instances from lines data
|
|
456
|
+
lines = [Line(**line_data) for line_data in lines_data]
|
|
457
|
+
|
|
458
|
+
# Return an instance of the calling class, which may be DisplaySpec or any of its subclasses
|
|
459
|
+
return cls(options=options, lines=lines)
|
|
460
|
+
|
|
461
|
+
def to_json(self,display_type) -> str:
|
|
462
|
+
"""
|
|
463
|
+
Converts the DisplaySpec instance into a JSON string.
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
str: A JSON string representation of the DisplaySpec instance.
|
|
467
|
+
"""
|
|
468
|
+
# Convert DisplaySpec instance to dictionary
|
|
469
|
+
|
|
470
|
+
display_spec_dict = {"display_type":display_type, "options": asdict(self.options), "lines": [asdict(line) for line in self.lines]}
|
|
471
|
+
# print(display_spec_dict)
|
|
472
|
+
# Serialize the dictionary to a JSON string
|
|
473
|
+
return json.dumps(display_spec_dict, indent=4)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@dataclass
|
|
479
|
+
class DisplayDef:
|
|
480
|
+
mmodel : Any = None
|
|
481
|
+
spec : DisplaySpec = field(default_factory=DisplaySpec)
|
|
482
|
+
name : str = ''
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def __post_init__(self):
|
|
486
|
+
self.options = self.spec.options
|
|
487
|
+
self.lines = self.spec.lines
|
|
488
|
+
|
|
489
|
+
try:
|
|
490
|
+
self.var_description = self.mmodel.defsub(self.mmodel.var_description | self.options.custom_description )
|
|
491
|
+
except:
|
|
492
|
+
self.var_description = {}
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
self.name = (self.name if self.name else self.options.name).replace(' ','_')
|
|
496
|
+
self.options.name = self.name
|
|
497
|
+
self.timeslice = self.options.timeslice if self.options.timeslice else []
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def set_name(self,name):
|
|
501
|
+
self.name = name.replace(' ','_')
|
|
502
|
+
self.options.name = self.name
|
|
503
|
+
return self
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@property
|
|
508
|
+
def get_report_smpl(self):
|
|
509
|
+
if type(self.mmodel.current_per[0]) == np.int64:
|
|
510
|
+
report_smpl = (int(self.mmodel.current_per[0]),int(self.mmodel.current_per[-1]))
|
|
511
|
+
else:
|
|
512
|
+
report_smpl = (str(self.mmodel.current_per[0]),str(self.mmodel.current_per[-1]) )
|
|
513
|
+
return report_smpl
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
@property
|
|
517
|
+
def save_spec(self):
|
|
518
|
+
display_type = self.__class__.__name__
|
|
519
|
+
new_spec = self.spec + Options(smpl = self.report_smpl)
|
|
520
|
+
# print(f"\n{Options(smpl = self.report_smpl)=}")
|
|
521
|
+
# print(f"\n{new_spec=}")
|
|
522
|
+
# print(f"\n{new_spec=}")
|
|
523
|
+
# out = self.spec.to_json(display_type)
|
|
524
|
+
out = new_spec.to_json(display_type)
|
|
525
|
+
return out
|
|
526
|
+
|
|
527
|
+
def get_rowdes(self,df,line,row=True):
|
|
528
|
+
thisdf = df.copy() if row else df.copy().T
|
|
529
|
+
if self.options.rename or line.rename:
|
|
530
|
+
rowdes = [self.var_description[v] for v in thisdf.index]
|
|
531
|
+
else:
|
|
532
|
+
rowdes = [v for v in thisdf.index]
|
|
533
|
+
|
|
534
|
+
if self.options.decorate :
|
|
535
|
+
match line.showtype:
|
|
536
|
+
case 'growth':
|
|
537
|
+
rowdes = [f'{des}, % growth' for des in rowdes]
|
|
538
|
+
|
|
539
|
+
case 'gdppct':
|
|
540
|
+
rowdes = [f'{des.split(",")[0].split("mill")[0]}, % of GDP' for des in rowdes]
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
case _:
|
|
544
|
+
rowdes = rowdes
|
|
545
|
+
# print(f'get_rowdes {line=}')
|
|
546
|
+
thisdf.index = rowdes
|
|
547
|
+
dfout = thisdf if row else thisdf.T
|
|
548
|
+
return dfout
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def pdf(self,pdfopen=False,show=True,width=WIDTH,height=HEIGHT,typesetter='xelatex -interaction=batchmode -no-shell-escape'):
|
|
557
|
+
try:
|
|
558
|
+
repo = LatexRepo(self.latex ,name=self.name)
|
|
559
|
+
return repo.pdf(pdfopen,show,width,height,typesetter)
|
|
560
|
+
except Exception as e:
|
|
561
|
+
print(f"⚠️ Warning: PDF generation failed: {e}")
|
|
562
|
+
return None
|
|
563
|
+
|
|
564
|
+
def __add__(self, other):
|
|
565
|
+
"""
|
|
566
|
+
Combines two DisplayDef instances into a new DisplayDef with combined specifications.
|
|
567
|
+
|
|
568
|
+
:param other: Another DisplayDef instance to add.
|
|
569
|
+
:return: A new DisplayDef instance with merged specifications.
|
|
570
|
+
"""
|
|
571
|
+
if isinstance(other, str):
|
|
572
|
+
out = DisplayContainerDef(mmodel=self.mmodel,reports= [self] + [get_DisplayTextDef(self.mmodel,other)])
|
|
573
|
+
|
|
574
|
+
else:
|
|
575
|
+
|
|
576
|
+
out = DisplayContainerDef(mmodel=self.mmodel,reports= [self,other])
|
|
577
|
+
|
|
578
|
+
# Create a new DisplayDef with the combined specifications
|
|
579
|
+
return out
|
|
580
|
+
|
|
581
|
+
def __radd__(self, other):
|
|
582
|
+
"""
|
|
583
|
+
Combines two DisplayDef instances into a new DisplayDef with combined specifications.
|
|
584
|
+
|
|
585
|
+
:param other: Another DisplayDef instance to add.
|
|
586
|
+
:return: A new DisplayDef instance with merged specifications.
|
|
587
|
+
"""
|
|
588
|
+
if isinstance(other, str):
|
|
589
|
+
# If the left-hand side operand is a string, this method will be called
|
|
590
|
+
linstance = get_DisplayTextDef(self.mmodel,input_string = other)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
out = DisplayContainerDef(mmodel=self.mmodel,reports= [linstance,self])
|
|
594
|
+
|
|
595
|
+
elif isinstance(other, int):
|
|
596
|
+
# used in sums
|
|
597
|
+
return self
|
|
598
|
+
|
|
599
|
+
else:
|
|
600
|
+
return NotImplemented
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
# out = DisplayContainerDef(mmodel=self.mmodel,reports= [linstance,self])
|
|
604
|
+
|
|
605
|
+
# Create a new DisplayDef with the combined specifications
|
|
606
|
+
return out
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def __or__(self, other):
|
|
612
|
+
"""
|
|
613
|
+
Combines two DisplayDef instances into a new DisplayDef with combined specifications.
|
|
614
|
+
|
|
615
|
+
:param other: Another DisplayDef instance to add.
|
|
616
|
+
:return: A new DisplayDef instance with merged specifications.
|
|
617
|
+
"""
|
|
618
|
+
if not isinstance(other, self.__class__):
|
|
619
|
+
return NotImplemented
|
|
620
|
+
|
|
621
|
+
# Combine options using the existing __add__ method of DisplaySpec
|
|
622
|
+
new_spec = self.spec + other.spec
|
|
623
|
+
# print(f'{self.spec=}')
|
|
624
|
+
# print(f'{new_spec=}')
|
|
625
|
+
# Merge names if they differ, separated by a comma
|
|
626
|
+
new_name = self.name if self.name == other.name else f"{self.name}_{other.name}"
|
|
627
|
+
# print(f'{new_name=}')
|
|
628
|
+
|
|
629
|
+
# Create a new DisplayDef with the combined specifications
|
|
630
|
+
return self.__class__(mmodel=self.mmodel, spec=new_spec, name=new_name)
|
|
631
|
+
|
|
632
|
+
def __and__(self, other):
|
|
633
|
+
if hasattr(other,'latex'):
|
|
634
|
+
# If the other object is an instance of LaTeXHolder, concatenate their LaTeX strings
|
|
635
|
+
return LatexRepo(latex =self.latex + '\n' + other.latex,name=self.name)
|
|
636
|
+
elif isinstance(other, str):
|
|
637
|
+
# If the other object is a string, assume it's a raw LaTeX string and concatenate
|
|
638
|
+
return LatexRepo(latex = self.latex + '\n' + DisplayLatexDef(DisplaySpec(options = Options(latex_text=other,name=self.name))).latex )
|
|
639
|
+
else:
|
|
640
|
+
# If the other object is neither a LaTeXHolder instance nor a string, raise an error
|
|
641
|
+
raise ValueError("Can only add another LaTeXHolder instance or a raw LaTeX string.")
|
|
642
|
+
|
|
643
|
+
def __rand__(self, other):
|
|
644
|
+
if isinstance(other, str):
|
|
645
|
+
# If the left-hand side operand is a string, this method will be called
|
|
646
|
+
return LatexRepo(latex = DisplayLatexDef(spec = DisplaySpec(options = Options(latex_text=other,name=self.name))).latex + '\n' + self.latex)
|
|
647
|
+
else:
|
|
648
|
+
# Handling unexpected types gracefully
|
|
649
|
+
raise ValueError("Left operand must be a string for LaTeX concatenation.")
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def _ipython_display_(self):
|
|
656
|
+
display(self.out_html)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def set_options(self,**kwargs):
|
|
661
|
+
spec = self.spec + kwargs
|
|
662
|
+
try:
|
|
663
|
+
#We want to keep the smpl which shere used originaly if it is a tab
|
|
664
|
+
with self.mmodel.set_smpl(self.df.columns[0],self.df.columns[-1]):
|
|
665
|
+
out = self.__class__(mmodel=self.mmodel,spec= spec)
|
|
666
|
+
except:
|
|
667
|
+
out = self.__class__(mmodel=self.mmodel,spec= spec)
|
|
668
|
+
|
|
669
|
+
return out
|
|
670
|
+
|
|
671
|
+
def figwrap(self,chart,pgf=False):
|
|
672
|
+
latex_dir = Path(f'../{self.name}')
|
|
673
|
+
|
|
674
|
+
if pgf:
|
|
675
|
+
out = r'''
|
|
676
|
+
\begin{figure}[htbp]
|
|
677
|
+
\centering
|
|
678
|
+
\resizebox{\textwidth}{!}'''
|
|
679
|
+
out = out + r'{\input{' +fr'"{(latex_dir / chart).as_posix()}.pgf"'+'}}'
|
|
680
|
+
out = out + fr'''
|
|
681
|
+
\caption{{{self.titledic[chart]}}}
|
|
682
|
+
\end{{figure}} '''
|
|
683
|
+
else: # for pandoc and word
|
|
684
|
+
out = r'''
|
|
685
|
+
\begin{figure}[htbp]
|
|
686
|
+
\centering
|
|
687
|
+
'''
|
|
688
|
+
out = out + r'\includegraphics[width=\textwidth]{' +fr'"{(latex_dir / chart).as_posix()}.png"'+'}'
|
|
689
|
+
caption = self.titledic[chart].replace("_",r'\_').replace('%',r'\%')
|
|
690
|
+
out = out + fr'''
|
|
691
|
+
\caption{{{caption}}}
|
|
692
|
+
\end{{figure}} '''
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
return out
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
@dataclass
|
|
702
|
+
class LatexRepo:
|
|
703
|
+
latex: str = ""
|
|
704
|
+
name : str ='latex_test'
|
|
705
|
+
|
|
706
|
+
def set_name(self,name):
|
|
707
|
+
self.name = name.replace(' ','_')
|
|
708
|
+
return self
|
|
709
|
+
|
|
710
|
+
def latexwrap(self):
|
|
711
|
+
|
|
712
|
+
latex_pre = r'''\documentclass{article}
|
|
713
|
+
\usepackage{booktabs}
|
|
714
|
+
\usepackage{caption} % Include the caption package
|
|
715
|
+
\captionsetup{justification=raggedright,singlelinecheck=false}
|
|
716
|
+
\usepackage{graphicx}
|
|
717
|
+
\usepackage{pgf}
|
|
718
|
+
\usepackage{lscape}
|
|
719
|
+
\usepackage{amsmath,amssymb}
|
|
720
|
+
\usepackage{placeins} % for \FloatBarrier
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
\begin{document}
|
|
724
|
+
|
|
725
|
+
'''
|
|
726
|
+
|
|
727
|
+
latex_post = r'''
|
|
728
|
+
\end{document}
|
|
729
|
+
'''
|
|
730
|
+
out = latex_pre + self.latex + latex_post
|
|
731
|
+
return out
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def pdf(self,pdfopen=False,show=True,width=WIDTH,height=HEIGHT,
|
|
736
|
+
typesetter='xelatex -interaction=batchmode -no-shell-escape '):
|
|
737
|
+
|
|
738
|
+
"""
|
|
739
|
+
Generates a PDF file from the LaTeX content and optionally displays it.
|
|
740
|
+
|
|
741
|
+
This method creates a directory for LaTeX files, writes the LaTeX content
|
|
742
|
+
to a `.tex` file, and uses a specified typesetter to compile it into a PDF.
|
|
743
|
+
The resulting PDF can be displayed in an `IFrame` or opened in the default
|
|
744
|
+
PDF viewer.
|
|
745
|
+
|
|
746
|
+
Requires that miktex or another appropiate latex framework is installed.
|
|
747
|
+
|
|
748
|
+
Inspect the latex source by specifying: typesetter='texworks'
|
|
749
|
+
|
|
750
|
+
The files are located in the folder called latex/{name}
|
|
751
|
+
|
|
752
|
+
- The .tex file is called {name.tex}
|
|
753
|
+
- the .pdf file is called {name.pdf}
|
|
754
|
+
|
|
755
|
+
Args:
|
|
756
|
+
pdfopen (bool): If True, opens the generated PDF file in the default viewer.
|
|
757
|
+
show (bool): If True, shows the pdf as a IFrame in Jupyter notebook .
|
|
758
|
+
width (int): The width of the `IFrame` if `show` is True.
|
|
759
|
+
height (int): The height of the `IFrame` if `show` is True.
|
|
760
|
+
typesetter (str): The LaTeX engine to use for compilation (e.g., 'xelatex (default), pdflatex, texworks or latexmk').
|
|
761
|
+
|
|
762
|
+
Returns:
|
|
763
|
+
IFrame: An iframe displaying the PDF file if `show` is True.
|
|
764
|
+
|
|
765
|
+
Raises:
|
|
766
|
+
Exception: If the typesetter returns a non-zero exit code, indicating an error
|
|
767
|
+
in generating the PDF. Opens the directory containing the LaTeX file for inspection.
|
|
768
|
+
"""
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
latex_dir = Path(f'latex/{self.name}')
|
|
774
|
+
latex_dir.mkdir(parents=True, exist_ok=True)
|
|
775
|
+
|
|
776
|
+
latex_file = latex_dir / f'{self.name}.tex'
|
|
777
|
+
pdf_file = latex_dir / f'{self.name}.pdf'
|
|
778
|
+
|
|
779
|
+
latex_file.unlink(missing_ok=True)
|
|
780
|
+
pdf_file.unlink(missing_ok=True)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
# Now open the file for writing within the newly created directory
|
|
786
|
+
with open(latex_file, 'wt', encoding='utf-8') as f:
|
|
787
|
+
f.write(self.latexwrap()) # Assuming tab.fulllatexwidget is the content you want to write
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
# xx0 = run(f'pdflatex {self.name}.tex' ,cwd = f'{latex_dir}')
|
|
791
|
+
try:
|
|
792
|
+
|
|
793
|
+
xx0 = run(f'{typesetter} {self.name}.tex' ,cwd = f'{latex_dir}', shell=True)
|
|
794
|
+
except FileNotFoundError as e:
|
|
795
|
+
# Catch the FileNotFoundError and print a message
|
|
796
|
+
print(f"Error: {e}")
|
|
797
|
+
print(f'The typesetter "{typesetter}" was not found. Please check the name and file path and try again.')
|
|
798
|
+
|
|
799
|
+
return
|
|
800
|
+
except Exception as e:
|
|
801
|
+
# Catch any other exceptions
|
|
802
|
+
print(f"Preparing PDF file an unexpected error occurred: {e}")
|
|
803
|
+
return
|
|
804
|
+
|
|
805
|
+
# xx0 = run(f'latexmk -pdf -dvi- -ps- -f {self.name}.tex' ,cwd = f'{latex_dir}')
|
|
806
|
+
if xx0.returncode:
|
|
807
|
+
wb.open(latex_dir.absolute(), new=1)
|
|
808
|
+
|
|
809
|
+
raise Exception(f'Error creating PDF file, {xx0.returncode},\n is latex installed?. If so look in the latex file, {latex_file}')
|
|
810
|
+
|
|
811
|
+
if pdfopen:
|
|
812
|
+
fileurl = f'file://{pdf_file.resolve()}'
|
|
813
|
+
# print(fileurl)
|
|
814
|
+
wb.open(fileurl , new=2)
|
|
815
|
+
# from modelhelp import debug_var
|
|
816
|
+
if show:
|
|
817
|
+
# debug_var(show,pdf_file)
|
|
818
|
+
# debug_var(IFrame(pdf_file, width=width, height=height))
|
|
819
|
+
|
|
820
|
+
return IFrame(pdf_file, width=width, height=height)
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def __and__(self, other):
|
|
824
|
+
|
|
825
|
+
if isinstance(other,str):
|
|
826
|
+
other_latex = DisplayLatexDef(spec = DisplaySpec(options = Options(latex_text=other,name=self.name))).latex
|
|
827
|
+
|
|
828
|
+
else:
|
|
829
|
+
if hasattr(other,'latex'):
|
|
830
|
+
other_latex= other.latex
|
|
831
|
+
else:
|
|
832
|
+
raise Exception('Trying to join latex from object without latex content ')
|
|
833
|
+
out = LatexRepo(self.latex + other_latex )
|
|
834
|
+
return out
|
|
835
|
+
|
|
836
|
+
def __rand__(self, other):
|
|
837
|
+
if isinstance(other, str):
|
|
838
|
+
# If the left-hand side operand is a string, this method will be called
|
|
839
|
+
return LatexRepo(latex=DisplayLatex(spec = DisplaySpec(options = Options(latex_text=other,name=self.name))).latex + '\n' + self.latex, name=self.name)
|
|
840
|
+
else:
|
|
841
|
+
# Handling unexpected types gracefully
|
|
842
|
+
raise ValueError("Left operand must be a string for LaTeX concatenation.")
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def _repr_html_(self):
|
|
846
|
+
self.pdf(show=False)
|
|
847
|
+
pdf_file = f"latex/{self.name}/{self.name}.pdf"
|
|
848
|
+
return f'<iframe src="{pdf_file}" width={WIDTH} height={HEIGHT}></iframe>'
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
DisplayDef.pdf.__doc__ = LatexRepo.pdf.__doc__
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
@dataclass
|
|
855
|
+
class DisplayVarTableDef(DisplayDef):
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def __post_init__(self):
|
|
859
|
+
super().__post_init__() # Call the parent class's __post_init__
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
with self.mmodel.set_smpl(*self.options.smpl):
|
|
863
|
+
self.dfs = [self.make_var_df(line).astype('float') for line in self.lines ]
|
|
864
|
+
self.report_smpl = self.get_report_smpl
|
|
865
|
+
|
|
866
|
+
if self.options.transpose:
|
|
867
|
+
self.df = self.dfs[-1].T
|
|
868
|
+
...
|
|
869
|
+
else:
|
|
870
|
+
self.df = pd.concat( self.dfs )
|
|
871
|
+
# assert 1==2
|
|
872
|
+
return
|
|
873
|
+
|
|
874
|
+
def make_var_df(self, line):
|
|
875
|
+
showtype = line.showtype
|
|
876
|
+
diftype = line.diftype
|
|
877
|
+
|
|
878
|
+
with self.mmodel.keepswitch(switch=True):
|
|
879
|
+
|
|
880
|
+
# Pre-process for cases that use linevars and linedes
|
|
881
|
+
if line.textlinetype in ['textline']:
|
|
882
|
+
linedf = pd.DataFrame(np.nan , index=self.mmodel.current_per, columns=[line.centertext]).T
|
|
883
|
+
self.unitline = self.lines[0].centertext
|
|
884
|
+
else:
|
|
885
|
+
def getline(start_ofset= 0,**kvargs):
|
|
886
|
+
locallinedfdict = self.mmodel.keep_get_plotdict_new(pat=line.pat,showtype=showtype,
|
|
887
|
+
diftype = diftype,by_var=False)
|
|
888
|
+
if diftype == 'basedf':
|
|
889
|
+
locallinedf = next(iter((locallinedfdict.values()))).T
|
|
890
|
+
else:
|
|
891
|
+
locallinedf = next(iter(reversed(locallinedfdict.values()))).T
|
|
892
|
+
|
|
893
|
+
return locallinedf.loc[:,self.mmodel.current_per]
|
|
894
|
+
|
|
895
|
+
# print(line.mul)
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
linedf = getline() * line.mul
|
|
900
|
+
linedf = self.get_rowdes(linedf,line)
|
|
901
|
+
|
|
902
|
+
return(linedf)
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
@property
|
|
908
|
+
def df_str_old(self):
|
|
909
|
+
width = self.options.width
|
|
910
|
+
# df = self.df.copy( )
|
|
911
|
+
if self.options.transpose:
|
|
912
|
+
dec = self.lines[-1].dec
|
|
913
|
+
thisdf = self.df.loc[self.timeslice,:] if self.timeslice else self.df
|
|
914
|
+
# df_char = pd.DataFrame(' ', index=self.thisdf.index, columns=self.thisdf.columns)
|
|
915
|
+
df_char = thisdf.map(lambda x: " " * width if pd.isna(x) else f"{x:>{width},.{dec}f}".strip() )
|
|
916
|
+
else:
|
|
917
|
+
df_char = pd.DataFrame(' ', index=self.df.index, columns=self.df.columns)
|
|
918
|
+
|
|
919
|
+
format_decimal = [ line.dec for line,df in zip(self.lines,self.dfs) for row in range(len(df))]
|
|
920
|
+
for i, dec in enumerate(format_decimal):
|
|
921
|
+
df_char.iloc[i] = self.df.iloc[i].apply(lambda x: " " * width if pd.isna(x) else f"{x:>{width},.{dec}f}".strip() )
|
|
922
|
+
|
|
923
|
+
return df_char
|
|
924
|
+
|
|
925
|
+
@property
|
|
926
|
+
def df_str(self):
|
|
927
|
+
width = self.options.width
|
|
928
|
+
# df = self.df.copy( )
|
|
929
|
+
if self.options.transpose:
|
|
930
|
+
dec = self.lines[-1].dec
|
|
931
|
+
thisdf = self.df.loc[self.timeslice,:] if self.timeslice else self.df
|
|
932
|
+
df_char = pd.DataFrame(' ', index=thisdf.index, columns=thisdf.columns)
|
|
933
|
+
# for c in thisdf.columns:
|
|
934
|
+
# df_char.loc[:,c] = thisdf.loc[:,c].apply(lambda x: " " * width if pd.isna(x) else f"{x:>{width},.{dec}f}".strip() )
|
|
935
|
+
for i,_ in enumerate(thisdf.index):
|
|
936
|
+
df_char.iloc[i] = self.df.iloc[i].apply(lambda x: " " * width if pd.isna(x) else f"{x:>{width},.{dec}f}".strip() )
|
|
937
|
+
else:
|
|
938
|
+
df_char = pd.DataFrame(' ', index=self.df.index, columns=self.df.columns)
|
|
939
|
+
|
|
940
|
+
format_decimal = [ line.dec for line,df in zip(self.lines,self.dfs) for row in range(len(df))]
|
|
941
|
+
for i, dec in enumerate(format_decimal):
|
|
942
|
+
df_char.iloc[i] = self.df.iloc[i].apply(lambda x: " " * width if pd.isna(x) else f"{x:>{width},.{dec}f}".strip() )
|
|
943
|
+
|
|
944
|
+
return df_char
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
@property
|
|
949
|
+
def df_str_disp(self):
|
|
950
|
+
center = [ (line.textlinetype == 'textline' and line.centertext !='' ) for line,df in zip(self.lines,self.dfs) for row in range(len(df))]
|
|
951
|
+
center_index = [index+1 for index, value in enumerate(center) if value]
|
|
952
|
+
|
|
953
|
+
width = self.options.width
|
|
954
|
+
thisdf = self.df_str.loc[:,self.timeslice] if self.timeslice else self.df_str
|
|
955
|
+
|
|
956
|
+
rawdata = thisdf.to_string(max_cols= self.options.max_cols).split('\n')
|
|
957
|
+
data = center_title_under_years(rawdata,center_index)
|
|
958
|
+
# print(*data,sep='\n')
|
|
959
|
+
# rawdata[0],rawdata[1] = rawdata[1],rawdata[0]
|
|
960
|
+
out = '\n'.join(data)
|
|
961
|
+
return out
|
|
962
|
+
|
|
963
|
+
@property
|
|
964
|
+
def df_str_disp_transpose(self):
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
rawdata = self.df_str.to_string(max_cols= self.options.max_cols).split('\n')
|
|
968
|
+
data = [self.unitline ] +rawdata
|
|
969
|
+
data[0],data[1] = data[1],data[0]
|
|
970
|
+
data = center_title_under_years(data,title_row_index=[1],year_row_index=0)
|
|
971
|
+
out = '\n'.join(data)
|
|
972
|
+
return out
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
@property
|
|
976
|
+
def show(self):
|
|
977
|
+
if self.options.title:
|
|
978
|
+
print(self.mmodel.string_substitution(self.options.title))
|
|
979
|
+
print(self.df_str_disp_transpose if self.options.transpose else self.df_str_disp)
|
|
980
|
+
if self.options.foot:
|
|
981
|
+
print(self.options.foot)
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
@property
|
|
985
|
+
def sheetwidget(self):
|
|
986
|
+
|
|
987
|
+
return [self.out_html]
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
@property
|
|
991
|
+
def out_html(self):
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
return HTML(self.htmlwidget)
|
|
996
|
+
|
|
997
|
+
@property
|
|
998
|
+
def htmlwidget_old(self):
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
if self.options.transpose:
|
|
1002
|
+
thisdf = self.df_str.loc[self.timeslice,:] if self.timeslice else self.df_str
|
|
1003
|
+
else:
|
|
1004
|
+
thisdf = self.df_str.loc[:,self.timeslice] if self.timeslice else self.df_str
|
|
1005
|
+
center = [ (line.textlinetype == 'textline' and line.centertext !='' ) for line,df in zip(self.lines,self.dfs) for row in range(len(df))]
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
outsty = self.make_html_style(thisdf)
|
|
1009
|
+
|
|
1010
|
+
if self.options.title:
|
|
1011
|
+
outsty = outsty.set_caption(self.options.title)
|
|
1012
|
+
|
|
1013
|
+
if self.options.foot:
|
|
1014
|
+
out = add_footer_to_styler(outsty,self.options.foot)
|
|
1015
|
+
else:
|
|
1016
|
+
out = outsty.to_html(na_rep='')
|
|
1017
|
+
|
|
1018
|
+
if self.options.transpose:
|
|
1019
|
+
out = insert_centered_row(out,self.unitline,len(self.df.columns))
|
|
1020
|
+
|
|
1021
|
+
return out
|
|
1022
|
+
|
|
1023
|
+
@property
|
|
1024
|
+
def htmlwidget(self):
|
|
1025
|
+
endhtml = ''
|
|
1026
|
+
def tab_to_html(i,df,line):
|
|
1027
|
+
nonlocal endhtml
|
|
1028
|
+
out = ''
|
|
1029
|
+
html_all = self.mmodel.ibsstyle(df,use_tooltip=False,dec=line.dec).to_html()
|
|
1030
|
+
splitted_html = HTMLSplitData(html_all)
|
|
1031
|
+
if i == 0:
|
|
1032
|
+
caption = f'<caption>{self.mmodel.string_substitution(self.options.title)}</caption>' if self.options.title else ''
|
|
1033
|
+
out = splitted_html.text_before_thead + caption + '<thead>' + splitted_html.thead+'\n'
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
endhtml = splitted_html.text_after_tbody
|
|
1037
|
+
|
|
1038
|
+
if (line.textlinetype == 'textline' and line.centertext !='' ):
|
|
1039
|
+
col0 = '<tr><td <th class="row_heading level0 row0" > </td>'
|
|
1040
|
+
out = out + '\n'+col0 + f"<td colspan='{len(df.columns)}' style='text-align: center;position: sticky; top: 0; background: white; left: 0;'>{line.centertext}</td></tr>"
|
|
1041
|
+
|
|
1042
|
+
else:
|
|
1043
|
+
out = out + splitted_html.tbody
|
|
1044
|
+
|
|
1045
|
+
return out
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
if self.options.transpose:
|
|
1049
|
+
thisdf = self.df_str.loc[self.timeslice,:] if self.timeslice else self.df_str
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
outsty = self.make_html_style(thisdf)
|
|
1053
|
+
|
|
1054
|
+
if self.options.title:
|
|
1055
|
+
outsty = outsty.set_caption(self.options.title)
|
|
1056
|
+
|
|
1057
|
+
if self.options.foot:
|
|
1058
|
+
out = add_footer_to_styler(outsty,self.options.foot)
|
|
1059
|
+
else:
|
|
1060
|
+
out = outsty.to_html(na_rep='')
|
|
1061
|
+
|
|
1062
|
+
if self.options.transpose:
|
|
1063
|
+
out = insert_centered_row(out,self.unitline,len(self.df.columns))
|
|
1064
|
+
|
|
1065
|
+
else:
|
|
1066
|
+
thisdfs = [(i,df.loc[:,self.timeslice] if self.timeslice else df, line) for i,(df,line) in
|
|
1067
|
+
enumerate(zip(self.dfs,self.lines))]
|
|
1068
|
+
|
|
1069
|
+
out = '\n'.join([tab_to_html(i,df,line) for i,df,line in thisdfs])+'\n'
|
|
1070
|
+
if self.options.foot:
|
|
1071
|
+
foot = f"<tfoot><tr><td colspan='5' style='text-align: left;'>{self.options.foot}</td></tr></tfoot>"
|
|
1072
|
+
else:
|
|
1073
|
+
foot =''
|
|
1074
|
+
|
|
1075
|
+
out = out + foot + '</table>'
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
return out
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
@property
|
|
1085
|
+
def latex(self):
|
|
1086
|
+
return self.latex_transpose if self.options.transpose else self.latex_straight
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
@property
|
|
1090
|
+
def latex_straight(self):
|
|
1091
|
+
last_cols = 0
|
|
1092
|
+
rowlines = [ line for line,df in zip(self.lines,self.dfs) for row in range(len(df))]
|
|
1093
|
+
if self.timeslice:
|
|
1094
|
+
dfs = [self.df_str.loc[:,self.timeslice]]
|
|
1095
|
+
else:
|
|
1096
|
+
if self.options.chunk_size:
|
|
1097
|
+
dfs = [self.df_str.iloc[:, i:i+self.options.chunk_size] for i in range(0, self.df_str.shape[1], self.options.chunk_size)]
|
|
1098
|
+
else:
|
|
1099
|
+
if len(self.df_str.columns) > self.options.max_cols:
|
|
1100
|
+
|
|
1101
|
+
last_cols = self.options.last_cols
|
|
1102
|
+
first_cols = self.options.max_cols - last_cols
|
|
1103
|
+
|
|
1104
|
+
dfs = [pd.concat([self.df_str.iloc[:, :first_cols], self.df_str.iloc[:, -last_cols:]], axis=1)]
|
|
1105
|
+
else:
|
|
1106
|
+
dfs = [self.df_str]
|
|
1107
|
+
outlist = []
|
|
1108
|
+
for i,df in enumerate(dfs):
|
|
1109
|
+
ncol=len(df.columns)
|
|
1110
|
+
newindex = [fr'&\multicolumn{{{ncol}}}'+'{c}{' + f'{line.latexfont}' + '{' + df.index[i]+'}}'
|
|
1111
|
+
if line.textlinetype == 'textline'
|
|
1112
|
+
else df.index[i]
|
|
1113
|
+
for i, line in enumerate(rowlines)]
|
|
1114
|
+
|
|
1115
|
+
df.index = newindex
|
|
1116
|
+
tabformat = 'l'+'r'*(ncol-last_cols) + ( ('|'+'r'*last_cols) if last_cols else '')
|
|
1117
|
+
outlist = outlist + [df.style.format(lambda x:x) \
|
|
1118
|
+
.set_caption(self.mmodel.string_substitution(self.options.title) + ('' if i == 0 else ' - continued ')) \
|
|
1119
|
+
.to_latex(hrules=True, position='ht', column_format=tabformat).replace('%',r'\%').replace('US$',r'US\$').replace('...',r'\dots')
|
|
1120
|
+
.replace(r'\caption{',r'\caption{')] # to be used if no numbering
|
|
1121
|
+
|
|
1122
|
+
# print(outlist)
|
|
1123
|
+
out = r' '.join(outlist)
|
|
1124
|
+
|
|
1125
|
+
out = '\n'.join(l.replace('& ','') if 'multicolum' in l else l for l in out.split('\n'))
|
|
1126
|
+
if self.options.foot:
|
|
1127
|
+
out = out.replace(r'\end{tabular}', r'\end{tabular}'+'\n'+rf'\caption*{{{self.options.foot}}}')
|
|
1128
|
+
|
|
1129
|
+
if self.options.landscape:
|
|
1130
|
+
out= r'\begin{landscape}'+'\n' + out + r'\end{landscape}' +'\n'
|
|
1131
|
+
|
|
1132
|
+
return out
|
|
1133
|
+
|
|
1134
|
+
@property
|
|
1135
|
+
def latex_transpose_old(self):
|
|
1136
|
+
|
|
1137
|
+
df = (self.df_str.loc[:,self.timeslice] if self.timeslice else self.df_str).T
|
|
1138
|
+
multi_df = create_column_multiindex(df)
|
|
1139
|
+
tabformat = 'l'+'r'*len(multi_df.columns)
|
|
1140
|
+
|
|
1141
|
+
latex_df = (multi_df.style.format(lambda x:x)
|
|
1142
|
+
.set_caption(self.options.title)
|
|
1143
|
+
.to_latex(hrules=True, position='ht', column_format=tabformat)
|
|
1144
|
+
.replace('%',r'\%').replace('US$',r'US\$').replace('...',r'\dots') )
|
|
1145
|
+
out = latex_df
|
|
1146
|
+
data = out.split('\n')
|
|
1147
|
+
# print(*[f'{i} {d}' for i,d in enumerate(data)],sep='\n')
|
|
1148
|
+
data[6],data[4],data[5] = data [4],data[5],data[6]
|
|
1149
|
+
# print(*[f'{i} {d}' for i,d in enumerate(data)],sep='\n')
|
|
1150
|
+
out = '\n'.join(data)
|
|
1151
|
+
out = '\n'.join(l.replace('{r}','{c}') if 'multicolum' in l else l for l in out.split('\n'))
|
|
1152
|
+
out = out.replace(r'\caption{',r'\caption{')
|
|
1153
|
+
if self.options.foot:
|
|
1154
|
+
out = out.replace(r'\end{tabular}', r'\end{tabular}'+'\n'+rf'\caption*{{{self.options.foot}}}')
|
|
1155
|
+
|
|
1156
|
+
return out
|
|
1157
|
+
|
|
1158
|
+
@property
|
|
1159
|
+
def latex_transpose(self):
|
|
1160
|
+
|
|
1161
|
+
thisdf = self.df_str.loc[self.timeslice,:] if self.timeslice else self.df_str
|
|
1162
|
+
tabformat = 'l'+'r'*len(thisdf.columns)
|
|
1163
|
+
|
|
1164
|
+
latex_df = (thisdf.style.format(lambda x:x)
|
|
1165
|
+
.set_caption(self.mmodel.string_substitution(self.options.title))
|
|
1166
|
+
.to_latex(hrules=True, position='ht', column_format=tabformat)
|
|
1167
|
+
.replace('%',r'\%').replace('US$',r'US\$').replace('...',r'\dots') )
|
|
1168
|
+
out = latex_df
|
|
1169
|
+
data = out.split('\n')
|
|
1170
|
+
# print(*[f'{i} {d}' for i,d in enumerate(data)],sep='\n')
|
|
1171
|
+
|
|
1172
|
+
unit_line_latex = fr'&\multicolumn{{{len(thisdf.columns)}}}'+'{c}{{' + self.unitline+r'}}\\'
|
|
1173
|
+
data = data[:6]+ [unit_line_latex] + data[6:]
|
|
1174
|
+
|
|
1175
|
+
# print(*[f'{i} {d}' for i,d in enumerate(data)],sep='\n')
|
|
1176
|
+
out = '\n'.join(data)
|
|
1177
|
+
out = '\n'.join(l.replace('{r}','{c}') if 'multicolum' in l else l for l in out.split('\n'))
|
|
1178
|
+
out = out.replace(r'\caption{',r'\caption{')
|
|
1179
|
+
if self.options.foot:
|
|
1180
|
+
out = out.replace(r'\end{tabular}', r'\end{tabular}'+'\n'+rf'\caption*{{{self.options.foot}}}')
|
|
1181
|
+
|
|
1182
|
+
if self.options.landscape:
|
|
1183
|
+
out= r'\begin{landscape}'+'\n' + out + r'\end{landscape}' +'\n'
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
return out
|
|
1187
|
+
|
|
1188
|
+
def make_html_style(self,df,use_tooltips =False ) :
|
|
1189
|
+
out = df.style.set_table_styles([
|
|
1190
|
+
{
|
|
1191
|
+
'selector': '.row_heading, .corner',
|
|
1192
|
+
'props': [
|
|
1193
|
+
('position', 'sticky'),
|
|
1194
|
+
('left', '0'),
|
|
1195
|
+
('z-index', '3'),
|
|
1196
|
+
('background-color', 'white'),
|
|
1197
|
+
('width', '300px'), # Set the width of the row headings
|
|
1198
|
+
('min-width', '200px'), # Ensure the minimum width is respected
|
|
1199
|
+
('max-width', '400px') # Ensure the maximum width is respected
|
|
1200
|
+
]
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
'selector': '.col_heading',
|
|
1204
|
+
'props': [
|
|
1205
|
+
('position', 'sticky'),
|
|
1206
|
+
('top', '0'),
|
|
1207
|
+
('z-index', '2'),
|
|
1208
|
+
('background-color', 'white')
|
|
1209
|
+
]
|
|
1210
|
+
},
|
|
1211
|
+
{
|
|
1212
|
+
'selector': 'th',
|
|
1213
|
+
'props': [
|
|
1214
|
+
('text-align', 'left') , # Align text to the left
|
|
1215
|
+
('background-color', 'white'), # Ensuring headers are not transparent
|
|
1216
|
+
('z-index', '2') # Headers z-index on par with column headings
|
|
1217
|
+
]
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
'selector': 'td', # Targeting data cells
|
|
1221
|
+
'props': [
|
|
1222
|
+
('z-index', '1'), # Lower z-index than headers
|
|
1223
|
+
]
|
|
1224
|
+
},
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
{
|
|
1228
|
+
'selector': 'caption',
|
|
1229
|
+
'props': [
|
|
1230
|
+
('font-size', '16px'), # Make the font larger
|
|
1231
|
+
('font-weight', 'bold') # Make the font bold
|
|
1232
|
+
]
|
|
1233
|
+
}
|
|
1234
|
+
],overwrite=True)
|
|
1235
|
+
if use_tooltips:
|
|
1236
|
+
tt = pd.DataFrame([[v for v in df.columns ]for t in df.index] ,index=df.index,columns=df.columns)
|
|
1237
|
+
try:
|
|
1238
|
+
out=out.set_tooltips(tt, props='visibility: hidden; position: absolute; z-index: 1; border: 1px solid #000066;'
|
|
1239
|
+
'background-color: white; color: #000066; font-size: 0.8em;width:100%'
|
|
1240
|
+
'transform: translate(0px, -24px); padding: 0.6em; border-radius: 0.5em;')
|
|
1241
|
+
except Exception as e:
|
|
1242
|
+
...
|
|
1243
|
+
out= out.set_caption(self.options.title)
|
|
1244
|
+
|
|
1245
|
+
return out
|
|
1246
|
+
|
|
1247
|
+
def make_html_style(self,df,use_tooltips =False ) :
|
|
1248
|
+
# out = self.mmodel.ibsstyle(self.df_str,use_tooltip=False)
|
|
1249
|
+
out = self.mmodel.ibsstyle(df,use_tooltip=False)
|
|
1250
|
+
|
|
1251
|
+
return out
|
|
1252
|
+
|
|
1253
|
+
def __repr__(self):
|
|
1254
|
+
return f"MyDataClass({self.options!r} {self.lines!r})"
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
@dataclass
|
|
1258
|
+
class DisplayKeepFigDef(DisplayDef):
|
|
1259
|
+
|
|
1260
|
+
|
|
1261
|
+
def __post_init__(self):
|
|
1262
|
+
super().__post_init__() # Call the parent class's __post_init__
|
|
1263
|
+
self.base_last = not self.options.scenarios
|
|
1264
|
+
# print(self.options.scenarios)
|
|
1265
|
+
# print(self.base_last)
|
|
1266
|
+
with self.mmodel.keepswitch(scenarios=self.options.scenarios,base_last = self.base_last):
|
|
1267
|
+
|
|
1268
|
+
with self.mmodel.set_smpl(*self.options.smpl):
|
|
1269
|
+
|
|
1270
|
+
self.dfs = [f for line in self.lines for f in self.make_df(line) ]
|
|
1271
|
+
|
|
1272
|
+
self.figs = self.make_figs(showfig=False)
|
|
1273
|
+
self.report_smpl = self.get_report_smpl
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
self.chart_names = list(self.figs.keys() )
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
return
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
def make_df(self, line):
|
|
1286
|
+
if line.textlinetype in ['textline']:
|
|
1287
|
+
# textdf = pd.DataFrame(float('nan'), index=self.mmodel.current_per, columns=[line.centertext]).T
|
|
1288
|
+
outlist = []
|
|
1289
|
+
else:
|
|
1290
|
+
# print(f'{line.pat=}')
|
|
1291
|
+
locallinedfdict = self.mmodel.keep_get_plotdict_new(
|
|
1292
|
+
pat=line.pat,
|
|
1293
|
+
showtype=line.showtype,
|
|
1294
|
+
diftype = line.diftype,
|
|
1295
|
+
by_var=line.by_var)
|
|
1296
|
+
|
|
1297
|
+
# print(f'before {locallinedfdict.keys()=}')
|
|
1298
|
+
if self.base_last or line.diftype == 'basedf':
|
|
1299
|
+
if line.by_var :
|
|
1300
|
+
if line.diftype == 'basedf':
|
|
1301
|
+
locallinedfdict = {k: df.iloc[:,[0]] for k,df in locallinedfdict.items() }
|
|
1302
|
+
else:
|
|
1303
|
+
locallinedfdict = {k: df.iloc[:,[-1]] for k,df in locallinedfdict.items() }
|
|
1304
|
+
else:
|
|
1305
|
+
if line.diftype == 'basedf':
|
|
1306
|
+
first_key = next(iter(locallinedfdict)) # First key
|
|
1307
|
+
locallinedfdict = {first_key: locallinedfdict[first_key]}
|
|
1308
|
+
else:
|
|
1309
|
+
last_key = next(reversed(locallinedfdict)) # Last key
|
|
1310
|
+
locallinedfdict = {last_key: locallinedfdict[last_key]}
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
# print(f'after {locallinedfdict.keys()=}')
|
|
1315
|
+
|
|
1316
|
+
outlist = [{'line':line, 'key':k ,
|
|
1317
|
+
'df' : self.get_rowdes(df.loc[self.mmodel.current_per,:],line,row=False)
|
|
1318
|
+
} for k,df in locallinedfdict.items() ]
|
|
1319
|
+
|
|
1320
|
+
return(outlist)
|
|
1321
|
+
|
|
1322
|
+
def make_figs(self,showfig=True):
|
|
1323
|
+
# def keep_plot(self, pat='*', start='', end='', start_ofset=0, end_ofset=0, showtype='level',
|
|
1324
|
+
# diff=False, diffpct=False, mul=1.0, title='Scenarios', legend=False, scale='linear',
|
|
1325
|
+
# yunit='', ylabel='', dec='', trans=None, showfig=True, kind='line', size=(10, 6),
|
|
1326
|
+
# vline=None, savefig='', by_var=True, dataonly=False, samefig=False, ncol=2):
|
|
1327
|
+
"""
|
|
1328
|
+
Generate and display plots for specified scenarios and variables.
|
|
1329
|
+
Returns:
|
|
1330
|
+
dict: A dictionary of Matplotlib figures, with keys being the variable names and values being the figure objects.
|
|
1331
|
+
|
|
1332
|
+
Raises:
|
|
1333
|
+
ZeroDivisionError: If no kept solution is available for plotting.
|
|
1334
|
+
"""
|
|
1335
|
+
# Function implementation...
|
|
1336
|
+
|
|
1337
|
+
# Function implementation...
|
|
1338
|
+
|
|
1339
|
+
# Figures are built with the object-oriented matplotlib API (Figure())
|
|
1340
|
+
# so they are NOT registered in pyplot's global figure manager. This
|
|
1341
|
+
# prevents the %matplotlib inline backend from auto-rendering them at
|
|
1342
|
+
# end-of-cell (which caused a spurious, stale duplicate plot in
|
|
1343
|
+
# addition to this object's own rendering).
|
|
1344
|
+
dfsres = self.dfs
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
number = len(dfsres)
|
|
1349
|
+
options = self.options
|
|
1350
|
+
|
|
1351
|
+
if options.samefig:
|
|
1352
|
+
...
|
|
1353
|
+
all_by_var = all([item['line'].by_var for item in dfsres])
|
|
1354
|
+
xcol = options.ncol
|
|
1355
|
+
xrow=-((-number )//options.ncol)
|
|
1356
|
+
figsize = (xcol*options.size[0],xrow*options.size[1])
|
|
1357
|
+
|
|
1358
|
+
# print(f'{size=} {figsize=}')
|
|
1359
|
+
fig = Figure(figsize=figsize)
|
|
1360
|
+
#gs = gridspec.GridSpec(xrow + 1, xcol, figure=fig) # One additional row for the legend
|
|
1361
|
+
|
|
1362
|
+
if options.legend and all_by_var:
|
|
1363
|
+
extra_row = 1
|
|
1364
|
+
row_heights = [1] * xrow + [0.5] # Assuming equal height for all plot rows, and half for the legend
|
|
1365
|
+
|
|
1366
|
+
else:
|
|
1367
|
+
extra_row = 0
|
|
1368
|
+
row_heights = [1] * xrow # Assuming equal height for all plot rows,
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
gs = gridspec.GridSpec(xrow + extra_row , xcol, figure=fig, height_ratios=row_heights)
|
|
1372
|
+
|
|
1373
|
+
fig.set_constrained_layout(True)
|
|
1374
|
+
|
|
1375
|
+
# Create axes for the plots
|
|
1376
|
+
axes = [fig.add_subplot(gs[i, j]) for i in range(xrow) for j in range(xcol)]
|
|
1377
|
+
if options.legend and all_by_var and not self.base_last:
|
|
1378
|
+
legend_ax = fig.add_subplot(gs[-1, :]) # Span the legend axis across the bottom
|
|
1379
|
+
|
|
1380
|
+
figs = {self.name : fig}
|
|
1381
|
+
|
|
1382
|
+
else:
|
|
1383
|
+
if 1:
|
|
1384
|
+
keys = format_list_with_numbers([f'{self.var_description[dr["key"]]}, {dr["line"].showtype} '+
|
|
1385
|
+
f'{dr["line"].diftype} '.replace('nodif','') for dr in dfsres ])
|
|
1386
|
+
#print(f'{keys}=')
|
|
1387
|
+
else:
|
|
1388
|
+
keys = format_list_with_numbers([dr['key'] for dr in dfsres ])
|
|
1389
|
+
...
|
|
1390
|
+
# figs_and_ax = {f'{self.name}_{i}' : plt.subplots(figsize=options.size) for i,v in enumerate(dfsres)}
|
|
1391
|
+
def _new_fig_ax():
|
|
1392
|
+
f = Figure(figsize=options.size)
|
|
1393
|
+
a = f.subplots()
|
|
1394
|
+
return f, a
|
|
1395
|
+
figs_and_ax = {v : _new_fig_ax() for v in keys}
|
|
1396
|
+
figs = {v : fig for v,(fig,ax) in figs_and_ax.items() }
|
|
1397
|
+
axes = [ ax for fig,ax in figs_and_ax.values() ]
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
for i,item in enumerate(dfsres):
|
|
1401
|
+
v = item['key']
|
|
1402
|
+
df = item['df']
|
|
1403
|
+
line = item['line']
|
|
1404
|
+
|
|
1405
|
+
mul = line.mul
|
|
1406
|
+
by_var= line.by_var
|
|
1407
|
+
aspct = ' as pct ' if line.diftype in {'difpct'} else ' '
|
|
1408
|
+
dftype = line.showtype.capitalize()
|
|
1409
|
+
dec = line.dec
|
|
1410
|
+
|
|
1411
|
+
ylabel = 'Percent' if (line.showtype in { 'growth','gdppct'} or line.diftype == 'difpct' ) else ''
|
|
1412
|
+
|
|
1413
|
+
# if by_var:
|
|
1414
|
+
# pretitle = (f'Difference{aspct}to "{list(self.mmodel.keep_solutions.keys())[0] }" for {dftype}:'
|
|
1415
|
+
# if (line.diftype in {'difpct', 'dif'}) else f'{dftype}:')
|
|
1416
|
+
# else:
|
|
1417
|
+
# pretitle = (f'Difference{aspct}to "{df.columns[0] }" for {dftype}:'
|
|
1418
|
+
# if (line.diftype in {'difpct','dif'}) else f'{dftype}:')
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
compare = f"{list(self.mmodel.keep_solutions.keys())[0] }" if by_var else f"{df.columns[0] }"
|
|
1422
|
+
var_name = v
|
|
1423
|
+
var_description = self.var_description[v]
|
|
1424
|
+
|
|
1425
|
+
default_title = line.default_ax_title_template_df if self.base_last else line.default_ax_title_template
|
|
1426
|
+
if self.base_last:
|
|
1427
|
+
ax_title_template = line.ax_title_template if line.ax_title_template else line.default_ax_title_template_df
|
|
1428
|
+
else:
|
|
1429
|
+
ax_title_template = line.ax_title_template if line.ax_title_template else line.default_ax_title_template
|
|
1430
|
+
|
|
1431
|
+
ax_title =ax_title_template.format(compare=compare,var_name = var_name,var_description=var_description).replace(r'\n','\n')
|
|
1432
|
+
|
|
1433
|
+
# print(v,ax_title_template)
|
|
1434
|
+
# title=(f'Difference{aspct}to "{df.columns[0] if not by_var else list(self.mmodel.keep_solutions.keys())[0] }" for {dftype}:'
|
|
1435
|
+
# if (line.diftype in {'difpct'}) else f'{dftype}:')
|
|
1436
|
+
|
|
1437
|
+
self.mmodel.plot_basis_ax(axes[i], v , df*mul, legend=options.legend,
|
|
1438
|
+
scale='linear', trans=self.var_description if self.options.rename else {},
|
|
1439
|
+
ax_title = ax_title ,
|
|
1440
|
+
yunit=line.yunit,
|
|
1441
|
+
ylabel='Percent' if (line.showtype in {'growth','gdppct'} or line.diftype in {'difpct'})else ylabel,
|
|
1442
|
+
xlabel='',kind = line.kind ,samefig=options.samefig and all_by_var,
|
|
1443
|
+
dec=dec)
|
|
1444
|
+
|
|
1445
|
+
if options.legend and self.base_last and axes[i].get_legend() is not None:
|
|
1446
|
+
axes[i].get_legend().remove()
|
|
1447
|
+
|
|
1448
|
+
for ax in axes[number:]:
|
|
1449
|
+
ax.set_visible(False)
|
|
1450
|
+
|
|
1451
|
+
|
|
1452
|
+
if options.samefig:
|
|
1453
|
+
fig.suptitle(self.mmodel.string_substitution(options.title) ,fontsize=20)
|
|
1454
|
+
|
|
1455
|
+
if options.legend and all_by_var and not self.base_last:
|
|
1456
|
+
handles, labels = axes[0].get_legend_handles_labels() # Assuming the first ax has the handles and labels
|
|
1457
|
+
legend_ax.legend(handles, labels, loc='center', ncol=3 if True else len(labels), fontsize='large')
|
|
1458
|
+
legend_ax.axis('off') # Hide the axis
|
|
1459
|
+
|
|
1460
|
+
else:
|
|
1461
|
+
for v,fig in figs.items() :
|
|
1462
|
+
if options.title:
|
|
1463
|
+
fig.suptitle(self.mmodel.string_substitution(options.title) ,fontsize=20)
|
|
1464
|
+
|
|
1465
|
+
|
|
1466
|
+
|
|
1467
|
+
if showfig:
|
|
1468
|
+
...
|
|
1469
|
+
for f in figs.values():
|
|
1470
|
+
display(f)
|
|
1471
|
+
|
|
1472
|
+
|
|
1473
|
+
return figs
|
|
1474
|
+
|
|
1475
|
+
@property
|
|
1476
|
+
def latex(self):
|
|
1477
|
+
latex_dir = Path(f'../{self.name}')
|
|
1478
|
+
|
|
1479
|
+
if self.options.samefig:
|
|
1480
|
+
chart = list(self.figs.keys())[0]
|
|
1481
|
+
self.titledic = {chart: self.options.title}
|
|
1482
|
+
else:
|
|
1483
|
+
self.titledic = {chart: fig.axes[0].get_title() for chart,fig in self.figs.items() }
|
|
1484
|
+
|
|
1485
|
+
for fig in self.figs.values():
|
|
1486
|
+
fig.axes[0].set_title('')
|
|
1487
|
+
## pgf may not work in new version of matplotlib
|
|
1488
|
+
self.mmodel.savefigs(figs=self.figs, location = './latex',
|
|
1489
|
+
experimentname = self.name ,extensions= ['png'],
|
|
1490
|
+
# experimentname = self.name ,extensions= ['png','pgf'],
|
|
1491
|
+
xopen=False)
|
|
1492
|
+
|
|
1493
|
+
if not self.options.samefig:
|
|
1494
|
+
for c,fig in self.figs.items():
|
|
1495
|
+
fig.axes[0].set_title(self.titledic[c])
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+
out = '\n'.join( self.figwrap(chart) for chart in self.chart_names)
|
|
1500
|
+
return out
|
|
1501
|
+
|
|
1502
|
+
def savefigs(self,**kwargs):
|
|
1503
|
+
'''
|
|
1504
|
+
Saves a collection of matplotlib figures to a specified directory.
|
|
1505
|
+
|
|
1506
|
+
Parameters:
|
|
1507
|
+
- location (str): The base folder in which to save the charts. Defaults to './graph'.
|
|
1508
|
+
- experimentname (str): A subfolder under 'location' where charts are saved. Defaults to 'experiment1'.
|
|
1509
|
+
- addname (str): An additional name added to each figure filename. Defaults to an empty string.
|
|
1510
|
+
- extensions (list): A list of string file extensions for saving the figures. Defaults to ['svg'].
|
|
1511
|
+
- xopen (bool): If True, open the saved figure locations in a web browser.
|
|
1512
|
+
|
|
1513
|
+
Returns:
|
|
1514
|
+
str: The absolute path to the folder where figures are saved.
|
|
1515
|
+
|
|
1516
|
+
Raises:
|
|
1517
|
+
Exception: If the folder cannot be created or a figure cannot be saved/opened.
|
|
1518
|
+
'''
|
|
1519
|
+
|
|
1520
|
+
return self.mmodel.savefigs(figs=self.figs,**kwargs)
|
|
1521
|
+
|
|
1522
|
+
|
|
1523
|
+
@property
|
|
1524
|
+
def sheetwidget(self):
|
|
1525
|
+
return [self.out_html ]
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
@property
|
|
1529
|
+
def out_html(self):
|
|
1530
|
+
figlist = {t: htmlwidget_fig(f) for t,f in self.figs.items() }
|
|
1531
|
+
out = tabwidget(figlist,tab=False,selected_index=0)
|
|
1532
|
+
return out.datawidget
|
|
1533
|
+
|
|
1534
|
+
|
|
1535
|
+
# def _ipython_display_(self):
|
|
1536
|
+
# display(self.out_html)
|
|
1537
|
+
|
|
1538
|
+
# display(self.out_html)
|
|
1539
|
+
|
|
1540
|
+
@property
|
|
1541
|
+
def show(self):
|
|
1542
|
+
for f in self.figs.values():
|
|
1543
|
+
display(f)
|
|
1544
|
+
|
|
1545
|
+
|
|
1546
|
+
@dataclass
|
|
1547
|
+
class DisplayTextDef(DisplayDef):
|
|
1548
|
+
|
|
1549
|
+
|
|
1550
|
+
def __post_init__(self):
|
|
1551
|
+
super().__post_init__() # Call the parent class's __post_init__
|
|
1552
|
+
self.latex_text = self.options.latex_text
|
|
1553
|
+
self.html_text = self.options.html_text
|
|
1554
|
+
self.text_text = self.options.text_text
|
|
1555
|
+
self.markdown_text = self.options.markdown_text
|
|
1556
|
+
self.report_smpl =('','')
|
|
1557
|
+
|
|
1558
|
+
def __or__(self, other):
|
|
1559
|
+
"""
|
|
1560
|
+
Combines two DisplayDef instances into a new DisplayDef with combined specifications.
|
|
1561
|
+
|
|
1562
|
+
:param other: Another DisplayDef instance to add.
|
|
1563
|
+
:return: A new DisplayDef instance with merged specifications.
|
|
1564
|
+
"""
|
|
1565
|
+
if isinstance(other, str):
|
|
1566
|
+
out = get_DisplayTextDef(self.mmodel,other)
|
|
1567
|
+
|
|
1568
|
+
elif isinstance(other, self.__class__):
|
|
1569
|
+
out = other
|
|
1570
|
+
else:
|
|
1571
|
+
return NotImplemented
|
|
1572
|
+
|
|
1573
|
+
# Combine options using the existing __add__ method of DisplaySpec
|
|
1574
|
+
|
|
1575
|
+
# Merge names if they differ, separated by a comma
|
|
1576
|
+
new_name = self.name if self.name == out.name else f"{self.name}_{out.name}"
|
|
1577
|
+
new_spec = DisplaySpec(options=Options(
|
|
1578
|
+
text_text=self.text_text + out.text_text ,
|
|
1579
|
+
html_text=self.html_text + out.html_text,
|
|
1580
|
+
latex_text=self.latex_text + out.latex_text,
|
|
1581
|
+
markdown_text=self.markdown_text + out.markdown_text,
|
|
1582
|
+
name='some_text'))
|
|
1583
|
+
# Create a new DisplayDef with the combined specifications
|
|
1584
|
+
return self.__class__(mmodel=self.mmodel, spec=new_spec, name=new_name)
|
|
1585
|
+
|
|
1586
|
+
|
|
1587
|
+
@property
|
|
1588
|
+
def latex(self) :
|
|
1589
|
+
return self.latex_text
|
|
1590
|
+
|
|
1591
|
+
|
|
1592
|
+
|
|
1593
|
+
@property
|
|
1594
|
+
def out_html(self):
|
|
1595
|
+
return HTML(self.mmodel.string_substitution(self.html_text))
|
|
1596
|
+
|
|
1597
|
+
@property
|
|
1598
|
+
def sheetwidget(self):
|
|
1599
|
+
tablist = [self.out_html]
|
|
1600
|
+
return tablist
|
|
1601
|
+
|
|
1602
|
+
|
|
1603
|
+
|
|
1604
|
+
@property
|
|
1605
|
+
def show(self):
|
|
1606
|
+
print(self.mmodel.string_substitution(self.text_text))
|
|
1607
|
+
|
|
1608
|
+
|
|
1609
|
+
|
|
1610
|
+
@dataclass
|
|
1611
|
+
class DisplayContainerDef:
|
|
1612
|
+
mmodel : Any = None
|
|
1613
|
+
reports: List[DisplayDef] = field(default_factory=list)
|
|
1614
|
+
name: str = 'Report_test'
|
|
1615
|
+
options: dict = field(default_factory=dict)
|
|
1616
|
+
|
|
1617
|
+
|
|
1618
|
+
def __add__(self, other):
|
|
1619
|
+
"""
|
|
1620
|
+
Combines two DisplayDef instances into a new DisplayDef with combined specifications.
|
|
1621
|
+
|
|
1622
|
+
:param other: Another DisplayDef instance to add.
|
|
1623
|
+
:return: A new DisplayDef instance with merged specifications.
|
|
1624
|
+
"""
|
|
1625
|
+
if isinstance(other, str):
|
|
1626
|
+
out = DisplayContainerDef(mmodel=self.mmodel,reports= self.reports + [get_DisplayTextDef(self.mmodel,other)])
|
|
1627
|
+
|
|
1628
|
+
else:
|
|
1629
|
+
out = DisplayContainerDef(mmodel=self.mmodel,reports= self.reports + [other])
|
|
1630
|
+
|
|
1631
|
+
# Create a new DisplayDef with the combined specifications
|
|
1632
|
+
return out
|
|
1633
|
+
|
|
1634
|
+
|
|
1635
|
+
def set_name(self,name):
|
|
1636
|
+
self.name = name.replace(' ','_')
|
|
1637
|
+
return self
|
|
1638
|
+
|
|
1639
|
+
def set_scenarios(self,scenarios):
|
|
1640
|
+
rs = [r.set_options(scenarios=scenarios) for r in self.reports]
|
|
1641
|
+
out = DisplayContainerDef(mmodel=self.mmodel,reports= rs)
|
|
1642
|
+
|
|
1643
|
+
return out
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
@property
|
|
1647
|
+
def latex(self):
|
|
1648
|
+
out = '\n'.join(l.latex for l in self.reports)
|
|
1649
|
+
return out
|
|
1650
|
+
|
|
1651
|
+
def pdf(self,pdfopen=False,show=True,width=WIDTH,height=HEIGHT,typesetter='xelatex -interaction=batchmode -no-shell-escape'):
|
|
1652
|
+
try:
|
|
1653
|
+
repo = LatexRepo(self.latex ,name=self.name)
|
|
1654
|
+
out = repo.pdf(pdfopen,show,width,height,typesetter)
|
|
1655
|
+
except:
|
|
1656
|
+
out= None
|
|
1657
|
+
|
|
1658
|
+
@property
|
|
1659
|
+
def spec_list(self):
|
|
1660
|
+
out = [r.save_spec for r in self.reports]
|
|
1661
|
+
return out
|
|
1662
|
+
|
|
1663
|
+
|
|
1664
|
+
@property
|
|
1665
|
+
def save_spec(self):
|
|
1666
|
+
display_type = self.__class__.__name__
|
|
1667
|
+
out = self.to_json(display_type)
|
|
1668
|
+
return out
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
def to_json(self,display_type):
|
|
1672
|
+
|
|
1673
|
+
display_spec_dict = {"display_type":display_type, "options": self.options,
|
|
1674
|
+
"name":self.name,
|
|
1675
|
+
"reports": [r.save_spec for r in self.reports] }
|
|
1676
|
+
|
|
1677
|
+
# Serialize the dictionary to a JSON string
|
|
1678
|
+
return json.dumps(display_spec_dict, indent=4)
|
|
1679
|
+
|
|
1680
|
+
@classmethod
|
|
1681
|
+
def reports_restore(cls,mmodel,json_string):
|
|
1682
|
+
reports_json_strings = json.loads(json_string)
|
|
1683
|
+
|
|
1684
|
+
out = cls(mmodel=mmodel,reports = [create_instance_from_json(mmodel,r) for r in reports_json_strings['reports']] )
|
|
1685
|
+
return out
|
|
1686
|
+
# print (*[r for r in reports_json_strings['reports']],sep='\n')
|
|
1687
|
+
|
|
1688
|
+
|
|
1689
|
+
@property
|
|
1690
|
+
def show(self):
|
|
1691
|
+
for r in self.reports:
|
|
1692
|
+
print('\n')
|
|
1693
|
+
r.show
|
|
1694
|
+
|
|
1695
|
+
|
|
1696
|
+
@property
|
|
1697
|
+
def sheetwidget(self):
|
|
1698
|
+
tablist = [ r for r in self.reports for s in r.sheetwidget]
|
|
1699
|
+
return tablist
|
|
1700
|
+
|
|
1701
|
+
|
|
1702
|
+
|
|
1703
|
+
@property
|
|
1704
|
+
def out_html(self):
|
|
1705
|
+
for r in self.reports:
|
|
1706
|
+
for c in r.sheetwidget:
|
|
1707
|
+
display(c)
|
|
1708
|
+
#return out.out_html
|
|
1709
|
+
|
|
1710
|
+
def _ipython_display_(self):
|
|
1711
|
+
_ = self.out_html
|
|
1712
|
+
|
|
1713
|
+
|
|
1714
|
+
DisplayContainerDef.pdf.__doc__ = LatexRepo.pdf.__doc__
|
|
1715
|
+
|
|
1716
|
+
|
|
1717
|
+
@dataclass
|
|
1718
|
+
class DisplayFigWrapDef(DisplayDef):
|
|
1719
|
+
|
|
1720
|
+
figs : Dict = field(default_factory=dict)
|
|
1721
|
+
extensions : List[str] = field(default_factory=lambda: ['svg','pgf'])
|
|
1722
|
+
|
|
1723
|
+
|
|
1724
|
+
def __post_init__(self):
|
|
1725
|
+
super().__post_init__() # Call the parent class's __post_init__
|
|
1726
|
+
|
|
1727
|
+
|
|
1728
|
+
self.titledic = {chart: fig.axes[0].get_title() for chart,fig in self.figs.items() }
|
|
1729
|
+
|
|
1730
|
+
self.newfigs= {chart : fig for chart,fig in self.figs.items() }
|
|
1731
|
+
for fig in self.newfigs.values():
|
|
1732
|
+
fig.axes[0].set_title('')
|
|
1733
|
+
|
|
1734
|
+
|
|
1735
|
+
self.mmodel.savefigs(figs=self.newfigs, location = './latex',
|
|
1736
|
+
experimentname = self.name ,extensions= self.extensions
|
|
1737
|
+
,xopen=False)
|
|
1738
|
+
self.charts = list(self.newfigs.keys() )
|
|
1739
|
+
|
|
1740
|
+
|
|
1741
|
+
return
|
|
1742
|
+
|
|
1743
|
+
return out
|
|
1744
|
+
|
|
1745
|
+
@property
|
|
1746
|
+
def latex(self):
|
|
1747
|
+
latex_dir = Path(f'../{self.name}')
|
|
1748
|
+
|
|
1749
|
+
out = '\n'.join( self.figwrap(chart) for chart in self.charts)
|
|
1750
|
+
return out
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
|
|
1754
|
+
@dataclass
|
|
1755
|
+
class HtmlSplitTable:
|
|
1756
|
+
html: str
|
|
1757
|
+
|
|
1758
|
+
def __post_init__(self):
|
|
1759
|
+
self.text_before_tbody, self.tbody, self.text_after_tbody = self.split_html()
|
|
1760
|
+
|
|
1761
|
+
def split_html(self):
|
|
1762
|
+
# Split HTML text into text before tbody, tbody, and text after tbody
|
|
1763
|
+
text_before_tbody = self.html.split('<tbody>')[0]
|
|
1764
|
+
tbody = self.html.split('<tbody>')[1].split('</tbody>')[0]
|
|
1765
|
+
text_after_tbody = self.html.split('</tbody>')[1]
|
|
1766
|
+
|
|
1767
|
+
return text_before_tbody, tbody, text_after_tbody
|
|
1768
|
+
|
|
1769
|
+
|
|
1770
|
+
@dataclass
|
|
1771
|
+
class HTMLSplitData:
|
|
1772
|
+
html: str
|
|
1773
|
+
|
|
1774
|
+
def __post_init__(self):
|
|
1775
|
+
self.text_before_thead, self.thead, self.tbody, self.text_after_tbody = self.split_html()
|
|
1776
|
+
|
|
1777
|
+
def split_html(self):
|
|
1778
|
+
# Split HTML text into parts before <thead>, <thead>, before <tbody>, <tbody>, and after <tbody>
|
|
1779
|
+
parts_before_thead = self.html.split('<thead>')
|
|
1780
|
+
text_before_thead = parts_before_thead[0]
|
|
1781
|
+
|
|
1782
|
+
thead_parts = parts_before_thead[1].split('</thead>')
|
|
1783
|
+
thead = '<thead>' + thead_parts[0] + '</thead>'
|
|
1784
|
+
text_before_tbody = thead_parts[1]
|
|
1785
|
+
|
|
1786
|
+
tbody_parts = text_before_tbody.split('<tbody>')
|
|
1787
|
+
tbody = '<tbody>' + tbody_parts[1].split('</tbody>')[0] + '</tbody>'
|
|
1788
|
+
text_after_tbody = tbody_parts[1].split('</tbody>')[1]
|
|
1789
|
+
|
|
1790
|
+
return text_before_thead, thead, tbody, text_after_tbody
|
|
1791
|
+
|
|
1792
|
+
def center_title_under_years(data, title_row_index=[1],year_row_index = 0):
|
|
1793
|
+
"""
|
|
1794
|
+
Center a title (specified by its index in the list) under the years row in a list of strings.
|
|
1795
|
+
|
|
1796
|
+
:param data: List of strings representing the data.
|
|
1797
|
+
:param title_row_index: Index of the title row in the list. Defaults to 1.
|
|
1798
|
+
:return: A new list of strings with the centered title.
|
|
1799
|
+
"""
|
|
1800
|
+
# Make a shallow copy of the list to avoid modifying the original list
|
|
1801
|
+
adjusted_data = data.copy()
|
|
1802
|
+
|
|
1803
|
+
# Find the start and end indices of the year values in the first row
|
|
1804
|
+
year_row = adjusted_data[year_row_index]
|
|
1805
|
+
start_index = len(year_row) - len(year_row.lstrip())
|
|
1806
|
+
end_index = len(year_row.rstrip())
|
|
1807
|
+
|
|
1808
|
+
# Calculate the total space available for centering
|
|
1809
|
+
total_space = end_index - start_index
|
|
1810
|
+
|
|
1811
|
+
# Center the title within this space
|
|
1812
|
+
for row_index in title_row_index:
|
|
1813
|
+
title = adjusted_data[row_index].replace('...',' ').strip() # Remove leading and trailing spaces
|
|
1814
|
+
centered_title = title.center(total_space)
|
|
1815
|
+
|
|
1816
|
+
# Replace the original title in the list with the centered title
|
|
1817
|
+
# Ensuring that the centered title is positioned correctly relative to the entire line
|
|
1818
|
+
adjusted_data[row_index] = f"{year_row[:start_index]}{centered_title}{year_row[end_index:]}"
|
|
1819
|
+
|
|
1820
|
+
return adjusted_data
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
def create_instance_from_json(mmodel,json_str: str):
|
|
1824
|
+
|
|
1825
|
+
def find_classes_matching_pattern():
|
|
1826
|
+
"""
|
|
1827
|
+
Finds and returns a dictionary of classes in the current module that match
|
|
1828
|
+
the pattern 'Display*Def', where the keys are the class names and the values
|
|
1829
|
+
are the actual class objects.
|
|
1830
|
+
"""
|
|
1831
|
+
pattern = re.compile(r'^Display.*Def$')
|
|
1832
|
+
class_dict = {name: cls for name, cls in globals().items()
|
|
1833
|
+
if re.match(pattern, name) and isinstance(cls, type)}
|
|
1834
|
+
return class_dict
|
|
1835
|
+
|
|
1836
|
+
data = json.loads(json_str)
|
|
1837
|
+
display_type = data['display_type']
|
|
1838
|
+
|
|
1839
|
+
|
|
1840
|
+
class_map = find_classes_matching_pattern()
|
|
1841
|
+
# print(class_map)
|
|
1842
|
+
|
|
1843
|
+
if display_type not in class_map:
|
|
1844
|
+
raise ValueError(f"Unsupported display type: {display_type}")
|
|
1845
|
+
|
|
1846
|
+
if display_type == 'DisplayContainerDef' :
|
|
1847
|
+
instance = DisplayContainerDef.reports_restore(mmodel,json_str)
|
|
1848
|
+
else:
|
|
1849
|
+
# Get the class
|
|
1850
|
+
cls = class_map[display_type]
|
|
1851
|
+
|
|
1852
|
+
# Process options and lines
|
|
1853
|
+
options = Options(**data['options'])
|
|
1854
|
+
lines = [Line(**line) for line in data['lines']]
|
|
1855
|
+
|
|
1856
|
+
# Create DisplaySpec instance
|
|
1857
|
+
spec = DisplaySpec(options=options, lines=lines)
|
|
1858
|
+
|
|
1859
|
+
# Create the display type instance (e.g., DisplayVarTableDef)
|
|
1860
|
+
instance = cls(mmodel,spec=spec)
|
|
1861
|
+
|
|
1862
|
+
return instance
|
|
1863
|
+
|
|
1864
|
+
def create_column_multiindex(df):
|
|
1865
|
+
"""
|
|
1866
|
+
Transforms the columns of a DataFrame into a MultiIndex, where the first column's name
|
|
1867
|
+
becomes the top level, and the names of the remaining columns serve as the second level.
|
|
1868
|
+
|
|
1869
|
+
Parameters:
|
|
1870
|
+
df (pd.DataFrame): The DataFrame to convert.
|
|
1871
|
+
|
|
1872
|
+
Returns:
|
|
1873
|
+
pd.DataFrame: A DataFrame with MultiIndex columns.
|
|
1874
|
+
"""
|
|
1875
|
+
# Retrieve the name of the first column to be used as the top level of the MultiIndex
|
|
1876
|
+
top_level_name = df.columns[0]
|
|
1877
|
+
|
|
1878
|
+
# Create a MultiIndex from the first column name and the names of the remaining columns
|
|
1879
|
+
multiindex_columns = [(top_level_name, col) for col in df.columns[1:]]
|
|
1880
|
+
|
|
1881
|
+
# Construct a new DataFrame with the MultiIndex columns
|
|
1882
|
+
multiindex_df = pd.DataFrame(df.iloc[:, 1:].values, index=df.index, columns=pd.MultiIndex.from_tuples(multiindex_columns))
|
|
1883
|
+
# print(multiindex_df)
|
|
1884
|
+
return multiindex_df
|
|
1885
|
+
|
|
1886
|
+
|
|
1887
|
+
def center_multiindex_headers(df):
|
|
1888
|
+
style = df.style.set_table_styles([
|
|
1889
|
+
{'selector': 'th.level0', 'props': [('text-align', 'center')]},
|
|
1890
|
+
# {'selector': 'th', 'props': [('font-size', '12pt')]},
|
|
1891
|
+
{
|
|
1892
|
+
'selector': '.row_heading, .corner',
|
|
1893
|
+
'props': [
|
|
1894
|
+
('position', 'sticky'),
|
|
1895
|
+
('left', '0'),
|
|
1896
|
+
('z-index', '3'),
|
|
1897
|
+
('background-color', 'white'),
|
|
1898
|
+
('width', '300px'), # Set the width of the row headings
|
|
1899
|
+
('min-width', '200px'), # Ensure the minimum width is respected
|
|
1900
|
+
('max-width', '400px') # Ensure the maximum width is respected
|
|
1901
|
+
]
|
|
1902
|
+
},
|
|
1903
|
+
{
|
|
1904
|
+
'selector': '.col_heading',
|
|
1905
|
+
'props': [
|
|
1906
|
+
('position', 'sticky'),
|
|
1907
|
+
('top', '0'),
|
|
1908
|
+
('z-index', '2'),
|
|
1909
|
+
('background-color', 'white')
|
|
1910
|
+
]
|
|
1911
|
+
},
|
|
1912
|
+
{
|
|
1913
|
+
'selector': 'th',
|
|
1914
|
+
'props': [
|
|
1915
|
+
('text-align', 'left') , # Align text to the left
|
|
1916
|
+
('background-color', 'white'), # Ensuring headers are not transparent
|
|
1917
|
+
('z-index', '2') # Headers z-index on par with column headings
|
|
1918
|
+
]
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
'selector': 'td', # Targeting data cells
|
|
1922
|
+
'props': [
|
|
1923
|
+
('z-index', '1'), # Lower z-index than headers
|
|
1924
|
+
]
|
|
1925
|
+
},
|
|
1926
|
+
|
|
1927
|
+
|
|
1928
|
+
{
|
|
1929
|
+
'selector': 'caption',
|
|
1930
|
+
'props': [
|
|
1931
|
+
('font-size', '16px'), # Make the font larger
|
|
1932
|
+
('font-weight', 'bold') # Make the font bold
|
|
1933
|
+
]
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
])
|
|
1937
|
+
return style
|
|
1938
|
+
|
|
1939
|
+
|
|
1940
|
+
def create_column_multiindex__(df):
|
|
1941
|
+
"""
|
|
1942
|
+
Transforms the columns of a DataFrame into a MultiIndex, where the first column's name
|
|
1943
|
+
becomes the top level, and the names of the remaining columns serve as the second level.
|
|
1944
|
+
The top level name will only be shown once in the MultiIndex representation.
|
|
1945
|
+
|
|
1946
|
+
Parameters:
|
|
1947
|
+
df (pd.DataFrame): The DataFrame to convert.
|
|
1948
|
+
|
|
1949
|
+
Returns:
|
|
1950
|
+
pd.DataFrame: A DataFrame with MultiIndex columns.
|
|
1951
|
+
"""
|
|
1952
|
+
# Retrieve the name of the first column to be used as the top level of the MultiIndex
|
|
1953
|
+
top_level_name = df.columns[0]
|
|
1954
|
+
|
|
1955
|
+
# Create a list of tuples for the MultiIndex where the first element is the top level name
|
|
1956
|
+
# and only use it once as the top level label for all subsequent columns
|
|
1957
|
+
multiindex_columns = [(top_level_name, col) if col != top_level_name else ('', col) for col in df.columns]
|
|
1958
|
+
|
|
1959
|
+
# Construct a new DataFrame with the MultiIndex columns
|
|
1960
|
+
multiindex_df = pd.DataFrame(df.values, index=df.index, columns=pd.MultiIndex.from_tuples(multiindex_columns))
|
|
1961
|
+
|
|
1962
|
+
return multiindex_df
|
|
1963
|
+
|
|
1964
|
+
def format_list_with_numbers(items):
|
|
1965
|
+
"""
|
|
1966
|
+
Format a list of items with numbered prefixes.
|
|
1967
|
+
|
|
1968
|
+
Args:
|
|
1969
|
+
items (list of str): The list of items to be formatted, where each item is a string separated by '|'.
|
|
1970
|
+
|
|
1971
|
+
Returns:
|
|
1972
|
+
list of str: The formatted list with numbered prefixes.
|
|
1973
|
+
"""
|
|
1974
|
+
# This dictionary will keep track of the counts of each prefix
|
|
1975
|
+
count_dict = {}
|
|
1976
|
+
# This will be the resulting list with formatted strings
|
|
1977
|
+
formatted_list = []
|
|
1978
|
+
|
|
1979
|
+
# First pass: count occurrences of each prefix
|
|
1980
|
+
for item in items:
|
|
1981
|
+
prefix = item.split('|')[0].strip()
|
|
1982
|
+
if prefix in count_dict:
|
|
1983
|
+
count_dict[prefix] += 1
|
|
1984
|
+
else:
|
|
1985
|
+
count_dict[prefix] = 1
|
|
1986
|
+
|
|
1987
|
+
# Second pass: format based on the count
|
|
1988
|
+
temp_dict = {} # This dictionary keeps track of the running count used for numbering
|
|
1989
|
+
for item in items:
|
|
1990
|
+
prefix = item.split('|')[0].strip()
|
|
1991
|
+
|
|
1992
|
+
# Determine the numbering based on the count
|
|
1993
|
+
if count_dict[prefix] > 1:
|
|
1994
|
+
if prefix in temp_dict:
|
|
1995
|
+
temp_dict[prefix] += 1
|
|
1996
|
+
else:
|
|
1997
|
+
temp_dict[prefix] = 1
|
|
1998
|
+
formatted_item = f"{prefix} {temp_dict[prefix]}"
|
|
1999
|
+
else:
|
|
2000
|
+
formatted_item = f"{prefix}"
|
|
2001
|
+
|
|
2002
|
+
formatted_list.append(formatted_item)
|
|
2003
|
+
|
|
2004
|
+
return formatted_list
|
|
2005
|
+
|
|
2006
|
+
def add_footer_to_styler(styler, footer_text):
|
|
2007
|
+
"""
|
|
2008
|
+
Extend a Pandas Styler object with a footer.
|
|
2009
|
+
|
|
2010
|
+
Args:
|
|
2011
|
+
styler (pandas.io.formats.style.Styler): The Pandas Styler object.
|
|
2012
|
+
footer_text (str): The text to be included in the footer.
|
|
2013
|
+
|
|
2014
|
+
Returns:
|
|
2015
|
+
str: The styled HTML with the added footer.
|
|
2016
|
+
"""
|
|
2017
|
+
# Convert styler to HTML and append a footer section
|
|
2018
|
+
styled_html = styler.to_html(na_rep='') # or styler.to_html() in older pandas versions
|
|
2019
|
+
footer_html = f"<tfoot><tr><td colspan='{len(styler.data.columns)}' style='text-align: left;'>{footer_text}</td></tr></tfoot>"
|
|
2020
|
+
|
|
2021
|
+
# Insert the footer just before the closing table tag
|
|
2022
|
+
styled_html_with_footer = styled_html.replace('</table>', f'{footer_html}</table>')
|
|
2023
|
+
return styled_html_with_footer
|
|
2024
|
+
|
|
2025
|
+
def split_text_html_latex(other):
|
|
2026
|
+
if other.startswith('<'):
|
|
2027
|
+
html,latex = other.split('<>')
|
|
2028
|
+
# If the left-hand side operand is a string, this method will be called
|
|
2029
|
+
linstance = DisplayTextDef(spec = DisplaySpec(options = Options(html_text=other,name='html Text')))
|
|
2030
|
+
|
|
2031
|
+
|
|
2032
|
+
else:
|
|
2033
|
+
linstance = DisplayTextDef(spec = DisplaySpec(options = Options(html_text=other,name='latex Text')))
|
|
2034
|
+
|
|
2035
|
+
def split_text(input_string):
|
|
2036
|
+
"""
|
|
2037
|
+
Split the input string based on the specified terminals.
|
|
2038
|
+
|
|
2039
|
+
Args:
|
|
2040
|
+
input_string (str): The input string to be split.
|
|
2041
|
+
|
|
2042
|
+
Returns:
|
|
2043
|
+
tuple: A tuple containing three substrings:
|
|
2044
|
+
- The first substring before any terminals.
|
|
2045
|
+
- The substring between <latex> and </latex> terminals.
|
|
2046
|
+
- The substring between <html> and </html> terminals.
|
|
2047
|
+
- The substring between <markdown> and </markdown> terminals.
|
|
2048
|
+
"""
|
|
2049
|
+
# Find the indices of terminals
|
|
2050
|
+
latex_start = input_string.find("<latex>")
|
|
2051
|
+
html_start = input_string.find("<html>")
|
|
2052
|
+
markdown_start = input_string.find("<markdown>")
|
|
2053
|
+
latex_end = input_string.find("</latex>")
|
|
2054
|
+
html_end = input_string.find("</html>")
|
|
2055
|
+
markdown_end = input_string.find("</markdown>")
|
|
2056
|
+
|
|
2057
|
+
# If terminals are not found, set their indices to the end of the string
|
|
2058
|
+
if latex_start == -1:
|
|
2059
|
+
latex_start = len(input_string)
|
|
2060
|
+
if html_start == -1:
|
|
2061
|
+
html_start = len(input_string)
|
|
2062
|
+
if markdown_start == -1:
|
|
2063
|
+
markdown_start = len(input_string)
|
|
2064
|
+
if latex_end == -1:
|
|
2065
|
+
latex_end = len(input_string)
|
|
2066
|
+
if html_end == -1:
|
|
2067
|
+
html_end = len(input_string)
|
|
2068
|
+
if markdown_end == -1:
|
|
2069
|
+
markdown_end = len(input_string)
|
|
2070
|
+
|
|
2071
|
+
# Extract the content between terminals
|
|
2072
|
+
latex_content = input_string[latex_start + len("<latex>"):latex_end]
|
|
2073
|
+
html_content = input_string[html_start + len("<html>"):html_end]
|
|
2074
|
+
markdown_content = input_string[markdown_start + len("<markdown>"):markdown_end]
|
|
2075
|
+
|
|
2076
|
+
# If no terminals are present, separate the first part as text
|
|
2077
|
+
text_end = min(html_start, markdown_start, latex_start)
|
|
2078
|
+
first_part = input_string[:text_end]
|
|
2079
|
+
return first_part, latex_content, html_content, markdown_content
|
|
2080
|
+
|
|
2081
|
+
def get_DisplayTextDef(mmodel,input_string):
|
|
2082
|
+
"""
|
|
2083
|
+
Create a DisplayTextDef object based on the input string.
|
|
2084
|
+
|
|
2085
|
+
Args:
|
|
2086
|
+
input_string (str): The input string to be processed.
|
|
2087
|
+
|
|
2088
|
+
Returns:
|
|
2089
|
+
DisplayTextDef: A DisplayTextDef object with the text, HTML, LaTeX, and Markdown content.
|
|
2090
|
+
"""
|
|
2091
|
+
# Find the indices of terminals and extract substrings
|
|
2092
|
+
text_obj = SplitTextResult(input_string)
|
|
2093
|
+
|
|
2094
|
+
# Create a DisplayTextDef object with the extracted content
|
|
2095
|
+
|
|
2096
|
+
out = DisplayTextDef(mmodel=mmodel,spec=DisplaySpec(options=Options(
|
|
2097
|
+
text_text=text_obj.text_text,
|
|
2098
|
+
html_text=text_obj. html_text,
|
|
2099
|
+
latex_text=text_obj.latex_text,
|
|
2100
|
+
markdown_text=text_obj.markdown_text,
|
|
2101
|
+
name='some_text')))
|
|
2102
|
+
return out
|
|
2103
|
+
|
|
2104
|
+
def insert_centered_row(html_text, centered_text, num_columns):
|
|
2105
|
+
# Find the index where the tbody starts
|
|
2106
|
+
tbody_start_index = html_text.find('<tbody>')+len('<tbody>')
|
|
2107
|
+
|
|
2108
|
+
# Construct the new row with centered text
|
|
2109
|
+
col0 = '<tr><td <th class="row_heading level0 row0" > </td>'
|
|
2110
|
+
|
|
2111
|
+
new_row = '\n'+col0 + f"<td colspan='{num_columns}' style='text-align: center;position: sticky; top: 0; background: white; left: 0;'>{centered_text}</td></tr>"
|
|
2112
|
+
|
|
2113
|
+
# Insert the new row at the beginning of the tbody
|
|
2114
|
+
|
|
2115
|
+
modified_html_text = html_text[:tbody_start_index] + new_row + html_text[tbody_start_index:]
|
|
2116
|
+
|
|
2117
|
+
return modified_html_text
|
|
2118
|
+
|
|
2119
|
+
|
|
2120
|
+
from dataclasses import dataclass
|
|
2121
|
+
|
|
2122
|
+
@dataclass
|
|
2123
|
+
class SplitTextResult:
|
|
2124
|
+
input_string: str
|
|
2125
|
+
text_text: str = ""
|
|
2126
|
+
latex_text: str = ""
|
|
2127
|
+
html_text: str = ""
|
|
2128
|
+
markdown_text: str = ""
|
|
2129
|
+
|
|
2130
|
+
def __post_init__(self):
|
|
2131
|
+
self.split_text(self.input_string)
|
|
2132
|
+
|
|
2133
|
+
def split_text(self, input_string):
|
|
2134
|
+
"""
|
|
2135
|
+
Split the input string based on the specified terminals.
|
|
2136
|
+
|
|
2137
|
+
Args:
|
|
2138
|
+
input_string (str): The input string to be split.
|
|
2139
|
+
|
|
2140
|
+
Returns:
|
|
2141
|
+
None
|
|
2142
|
+
"""
|
|
2143
|
+
# Find the indices of terminals
|
|
2144
|
+
latex_start = input_string.find("<latex>")
|
|
2145
|
+
html_start = input_string.find("<html>")
|
|
2146
|
+
markdown_start = input_string.find("<markdown>")
|
|
2147
|
+
latex_end = input_string.find("</latex>")
|
|
2148
|
+
html_end = input_string.find("</html>")
|
|
2149
|
+
markdown_end = input_string.find("</markdown>")
|
|
2150
|
+
|
|
2151
|
+
# If terminals are not found, set their indices to the end of the string
|
|
2152
|
+
if latex_start == -1:
|
|
2153
|
+
latex_start = len(input_string)
|
|
2154
|
+
if html_start == -1:
|
|
2155
|
+
html_start = len(input_string)
|
|
2156
|
+
if markdown_start == -1:
|
|
2157
|
+
markdown_start = len(input_string)
|
|
2158
|
+
if latex_end == -1:
|
|
2159
|
+
latex_end = len(input_string)
|
|
2160
|
+
if html_end == -1:
|
|
2161
|
+
html_end = len(input_string)
|
|
2162
|
+
if markdown_end == -1:
|
|
2163
|
+
markdown_end = len(input_string)
|
|
2164
|
+
|
|
2165
|
+
# If no terminals are present, separate the first part as text
|
|
2166
|
+
text_end = min(html_start, markdown_start, latex_start)
|
|
2167
|
+
self.text_text = input_string[:text_end]
|
|
2168
|
+
|
|
2169
|
+
# Extract the content between terminals
|
|
2170
|
+
latex_content_ = input_string[latex_start + len("<latex>"):latex_end]
|
|
2171
|
+
html_content_ = input_string[html_start + len("<html>"):html_end]
|
|
2172
|
+
markdown_content_ = input_string[markdown_start + len("<markdown>"):markdown_end]
|
|
2173
|
+
self.latex_text = latex_content_ if latex_content_ else self.text_text
|
|
2174
|
+
self.html_text = html_content_ if html_content_ else self.text_text
|
|
2175
|
+
self.markdown_text = markdown_content_ if markdown_content_ else self.text_text
|
|
2176
|
+
|
|
2177
|
+
# Test the implementation
|
|
2178
|
+
if __name__ == '__main__':
|
|
2179
|
+
|
|
2180
|
+
result = SplitTextResult("<html>Hello</html><latex>Latex Content</latex><markdown>Markdown Content</markdown>")
|
|
2181
|
+
print(result)
|
|
2182
|
+
|
|
2183
|
+
|
|
2184
|
+
|
|
2185
|
+
print(SplitTextResult('test'))
|
|
2186
|
+
print(SplitTextResult('test<latex>notest</latex<html>'))
|
|
2187
|
+
print(get_DisplayTextDef('kk','test'))
|