rephorm 1.0.1__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.
- rephorm/__init__.py +19 -0
- rephorm/decorators/__init__.py +0 -0
- rephorm/decorators/settings_validation.py +49 -0
- rephorm/dict/__init__.py +1 -0
- rephorm/dict/colors.py +22 -0
- rephorm/dict/legend_positions.py +136 -0
- rephorm/dict/styles.py +206 -0
- rephorm/dict/update_layout.py +129 -0
- rephorm/dict/update_traces.py +90 -0
- rephorm/object_mappers/__init__.py +0 -0
- rephorm/object_mappers/_globals.py +16 -0
- rephorm/object_mappers/_utilities/__init__.py +0 -0
- rephorm/object_mappers/_utilities/chart_mapper_utility.py +34 -0
- rephorm/object_mappers/_utilities/grid_mapper_utility.py +11 -0
- rephorm/object_mappers/_utilities/table_mapper_utility.py +59 -0
- rephorm/object_mappers/chapter_mapper.py +82 -0
- rephorm/object_mappers/chart_mapper.py +120 -0
- rephorm/object_mappers/chart_series_mapper.py +52 -0
- rephorm/object_mappers/grid_mapper.py +106 -0
- rephorm/object_mappers/page_break_mapper.py +8 -0
- rephorm/object_mappers/report_mapper.py +76 -0
- rephorm/object_mappers/table_mapper.py +191 -0
- rephorm/object_mappers/table_section_mapper.py +41 -0
- rephorm/object_mappers/table_series_mapper.py +79 -0
- rephorm/object_mappers/text_mapper.py +36 -0
- rephorm/object_params/__init__.py +0 -0
- rephorm/object_params/settings.py +184 -0
- rephorm/objects/__init__.py +0 -0
- rephorm/objects/_utilities/__init__.py +0 -0
- rephorm/objects/_utilities/settings_container.py +8 -0
- rephorm/objects/chapter.py +44 -0
- rephorm/objects/chart.py +70 -0
- rephorm/objects/chart_series.py +40 -0
- rephorm/objects/footnote.py +13 -0
- rephorm/objects/grid.py +57 -0
- rephorm/objects/page_break.py +13 -0
- rephorm/objects/report.py +196 -0
- rephorm/objects/table.py +76 -0
- rephorm/objects/table_section.py +48 -0
- rephorm/objects/table_series.py +36 -0
- rephorm/objects/text.py +35 -0
- rephorm/utility/PDF.py +80 -0
- rephorm/utility/__init__.py +0 -0
- rephorm/utility/add_style_prefix.py +30 -0
- rephorm/utility/fonts/OpenSans-Bold.ttf +0 -0
- rephorm/utility/fonts/OpenSans-BoldItalic.ttf +0 -0
- rephorm/utility/fonts/OpenSans-Italic.ttf +0 -0
- rephorm/utility/fonts/OpenSans.ttf +0 -0
- rephorm/utility/fonts/__init__.py +0 -0
- rephorm/utility/fonts/font_loading.py +74 -0
- rephorm/utility/is_set.py +9 -0
- rephorm/utility/merge/__init__.py +0 -0
- rephorm/utility/merge/chart_properties_manager.py +80 -0
- rephorm/utility/merge/merge_settings.py +134 -0
- rephorm/utility/merge/merge_styles.py +79 -0
- rephorm/utility/merge/pdf_merger.py +29 -0
- rephorm/utility/report/__init__.py +0 -0
- rephorm/utility/report/add_footnotes.py +46 -0
- rephorm/utility/report/cleanup_utility.py +19 -0
- rephorm/utility/report/footnotes_counter.py +15 -0
- rephorm/utility/report/image_utility.py +14 -0
- rephorm/utility/report/layout_utility.py +59 -0
- rephorm/utility/report/range_utility.py +94 -0
- rephorm/utility/report/report_utility.py +93 -0
- rephorm/utility/report/resolve_color.py +39 -0
- rephorm/utility/report/table_utility.py +71 -0
- rephorm/utility/report/title_utility.py +49 -0
- rephorm/utility/unit_converter.py +12 -0
- rephorm-1.0.1.dist-info/METADATA +41 -0
- rephorm-1.0.1.dist-info/RECORD +73 -0
- rephorm-1.0.1.dist-info/WHEEL +5 -0
- rephorm-1.0.1.dist-info/licenses/LICENSE +21 -0
- rephorm-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from irispie import Series as IrisSeries
|
|
2
|
+
|
|
3
|
+
from rephorm.object_mappers.chart_series_mapper import ChartSeriesMapper
|
|
4
|
+
from rephorm.objects._utilities.settings_container import SettingsContainer
|
|
5
|
+
from rephorm.decorators.settings_validation import validate_kwargs
|
|
6
|
+
from rephorm.utility.add_style_prefix import add_prefix_to_styles
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ChartSeries:
|
|
10
|
+
|
|
11
|
+
@validate_kwargs
|
|
12
|
+
def __init__( self, data: IrisSeries = None, **kwargs):
|
|
13
|
+
"""
|
|
14
|
+
:param data (IrisSeries, Required): Data for the chart series.
|
|
15
|
+
:key yaxis (str, Optional): Y-axis position for the chart series. Defaults to "left".
|
|
16
|
+
:key span (ir.Span): Span of the chart series.
|
|
17
|
+
:key show_legend (bool): to show/hide the legend for this data series.
|
|
18
|
+
:key legend (tuple): Specifies the legend labels for the series. For a single series, use ("Label",).
|
|
19
|
+
For multivariate series, provide multiple labels like ("Label 1", "Label 2", ...).
|
|
20
|
+
:key highlight (ir.Span): Span of the highlight.
|
|
21
|
+
:key series_type (str): Type of series to display - "line" for line charts, "bar" for bar charts,
|
|
22
|
+
"bar_contribution" for contribution bar charts; use "line" with markers_mode="markers" for marker-based charts.
|
|
23
|
+
:key markers_mode (str): Display style for data points - "lines+markers" (lines with symbols/dots),
|
|
24
|
+
"lines" (lines only), or "markers" (symbols/dots only).
|
|
25
|
+
:key marker_symbol (str): Symbol used for markers in the series. e.g., "asterisk".
|
|
26
|
+
:key update_traces (Dict): Traces configuration for the chart series. (Plotly specific) todo: check if this is used
|
|
27
|
+
:key styles (Dict): Styles dictionary for additional customization (for details refer to report object).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
self.data = data
|
|
31
|
+
self.settings = SettingsContainer(**kwargs)
|
|
32
|
+
|
|
33
|
+
if hasattr(self.settings, "styles"):
|
|
34
|
+
self.settings.styles = add_prefix_to_styles("chart.series", self.settings.styles)
|
|
35
|
+
|
|
36
|
+
def _get_mapper(self):
|
|
37
|
+
return ChartSeriesMapper(self)
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return f"{type(self).__name__}"
|
rephorm/objects/grid.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import copy
|
|
3
|
+
from typing import Text, Optional, Union, List
|
|
4
|
+
|
|
5
|
+
from rephorm.objects.table import Table
|
|
6
|
+
from rephorm.objects.text import Text
|
|
7
|
+
from rephorm.objects.page_break import PageBreak
|
|
8
|
+
from rephorm.objects.chart import Chart
|
|
9
|
+
from rephorm.object_mappers.grid_mapper import GridMapper
|
|
10
|
+
from rephorm.objects._utilities.settings_container import SettingsContainer
|
|
11
|
+
from rephorm.decorators.settings_validation import validate_kwargs
|
|
12
|
+
from rephorm.utility.add_style_prefix import add_prefix_to_styles
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Grid:
|
|
16
|
+
|
|
17
|
+
@validate_kwargs
|
|
18
|
+
def __init__(self, title: str = "", footnotes: Optional[List[str]] = None, **kwargs):
|
|
19
|
+
"""
|
|
20
|
+
:param title: (str) Title of the grid.
|
|
21
|
+
:param footnotes: (Tuple(str)) Footnotes for the title.
|
|
22
|
+
:key ncol (int): Number of columns in the grid.
|
|
23
|
+
:key nrow (int): Number of rows in the grid.
|
|
24
|
+
:key layout (Dict): layout configuration for the chart (see documentation).
|
|
25
|
+
:key styles (Dict): Styles dict (refer to documentation).
|
|
26
|
+
"""
|
|
27
|
+
self.title = title
|
|
28
|
+
self.CHILDREN = []
|
|
29
|
+
self.footnotes = footnotes
|
|
30
|
+
self.settings = SettingsContainer(**kwargs)
|
|
31
|
+
if hasattr(self.settings, "styles"):
|
|
32
|
+
self.settings.styles = add_prefix_to_styles("grid", self.settings.styles)
|
|
33
|
+
|
|
34
|
+
def add(
|
|
35
|
+
self,
|
|
36
|
+
grid_child : Union[Grid, Table, Text, PageBreak, Chart] = None,
|
|
37
|
+
):
|
|
38
|
+
|
|
39
|
+
#12/10/2024: We should have checks in the objects and not object mappers
|
|
40
|
+
# We would also not have problem with imports here, because this module itself is within OBJECTS
|
|
41
|
+
# Todo: Update latest docs, explaining it is not possible to nest grids within grids, and the reason why.
|
|
42
|
+
if grid_child is not None and isinstance(grid_child, (Table, Text, PageBreak, Chart)):
|
|
43
|
+
# grid_child._on_add_callback()
|
|
44
|
+
copy_grid_child = copy.deepcopy(grid_child)
|
|
45
|
+
self.CHILDREN.append(copy_grid_child)
|
|
46
|
+
else: raise Exception("Grid: grid child of wrong type or None")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get_mapper(self):
|
|
50
|
+
return GridMapper(self)
|
|
51
|
+
|
|
52
|
+
def __repr__(self):
|
|
53
|
+
return f"{type(self).__name__}"
|
|
54
|
+
|
|
55
|
+
def _on_add_callback(self):
|
|
56
|
+
pass
|
|
57
|
+
# This would be called by whoever is adding the grid object.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from typing import Optional, Dict, Text, Union
|
|
3
|
+
|
|
4
|
+
from rephorm.objects.grid import Grid
|
|
5
|
+
from rephorm.objects.table import Table
|
|
6
|
+
from rephorm.objects.text import Text
|
|
7
|
+
from rephorm.objects.page_break import PageBreak
|
|
8
|
+
from rephorm.objects.chart import Chart
|
|
9
|
+
from rephorm.objects.chapter import Chapter
|
|
10
|
+
|
|
11
|
+
from rephorm.objects._utilities.settings_container import SettingsContainer
|
|
12
|
+
from rephorm.utility.add_style_prefix import add_prefix_to_styles
|
|
13
|
+
from rephorm.utility.fonts.font_loading import get_fonts, add_custom_fonts
|
|
14
|
+
from rephorm.object_mappers.report_mapper import ReportMapper
|
|
15
|
+
from rephorm.utility.PDF import PDF
|
|
16
|
+
from rephorm.utility.merge.merge_settings import merge_settings
|
|
17
|
+
from rephorm.decorators.settings_validation import validate_kwargs
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Report:
|
|
21
|
+
@validate_kwargs
|
|
22
|
+
# Todo: delete report name from here, it is not needed since you can specify it on output method call
|
|
23
|
+
def __init__(self, title: str = "", subtitle: str = "", load_fonts: Optional[list[Dict[str, str]]] = None, abstract: str="", **kwargs):
|
|
24
|
+
# Todo: consider moving `load_fonts` to `kwargs` (Currently, it would be hard to validate entry/value if we move to kwargs, bcs of it's complex value type)
|
|
25
|
+
# Todo: add new setting `show_title_page=True` to control whether to create the front page or not (and make sure the header creation is aware of this parameter)
|
|
26
|
+
"""
|
|
27
|
+
**Params:**
|
|
28
|
+
- ``title`` (str): Title of the report. Placed in the front page.
|
|
29
|
+
- ``subtitle`` (str): Subtitle of the report. Placed in the front page after the title.
|
|
30
|
+
- ``load_fonts`` (list[Dict[str, str]]): Fonts to load for the report.
|
|
31
|
+
For ``load_fonts`` you need to specify font_family, font_style and font_path as dictionary.
|
|
32
|
+
|
|
33
|
+
**Kwargs:**
|
|
34
|
+
- ``orientation`` (str): Orientation of the report, e.g., 'P' for Portrait, 'L' for Landscape.
|
|
35
|
+
- ``unit`` (str): Measurement unit for the report, e.g., 'pt', 'mm', 'cm'.
|
|
36
|
+
- ``format`` (str): Paper format for the report, e.g., 'A4', 'Letter'.
|
|
37
|
+
- ``logo`` (str): Path to the logo file for the report.
|
|
38
|
+
- ``styles`` (Dict): Styles dictionary for additional customization. See below for keys.
|
|
39
|
+
|
|
40
|
+
**Styles Dictionary Keys:**
|
|
41
|
+
|
|
42
|
+
**Table**
|
|
43
|
+
*title*
|
|
44
|
+
- ``table.title.font_style`` (str)
|
|
45
|
+
- ``table.title.font_family`` (str)
|
|
46
|
+
- ``table.title.font_color`` (str)
|
|
47
|
+
- ``table.title.font_size`` (float)
|
|
48
|
+
|
|
49
|
+
*heading*
|
|
50
|
+
- ``table.heading.font_style`` (str)
|
|
51
|
+
- ``table.heading.font_family`` (str)
|
|
52
|
+
- ``table.heading.font_color`` (str)
|
|
53
|
+
- ``table.heading.font_size`` (float)
|
|
54
|
+
- ``table.heading.highlight_color`` (str): color name
|
|
55
|
+
|
|
56
|
+
*series*
|
|
57
|
+
- ``table.series.font_style`` (str)
|
|
58
|
+
- ``table.series.font_family`` (str)
|
|
59
|
+
- ``table.series.font_color`` (str)
|
|
60
|
+
- ``table.series.font_size`` (float)
|
|
61
|
+
- ``table.series.highlight_color`` (str): color name
|
|
62
|
+
|
|
63
|
+
**section**
|
|
64
|
+
- ``table.section.font_family`` (str)
|
|
65
|
+
- ``table.section.font_color`` (str)
|
|
66
|
+
- ``table.section.font_size`` (float)
|
|
67
|
+
- ``table.section.highlight_color`` (str): color name
|
|
68
|
+
|
|
69
|
+
**Chart:**
|
|
70
|
+
**general**
|
|
71
|
+
- ``chart.highlight_color`` (str)
|
|
72
|
+
- ``chart.grid_color`` (str)
|
|
73
|
+
- ``chart.bg_color`` (str): Background color of the chart.
|
|
74
|
+
- ``chart.chart_border_color`` (str): Border color of the chart. If border is applied
|
|
75
|
+
**legend**
|
|
76
|
+
- ``chart.legend.border_width`` (int):
|
|
77
|
+
- ``chart.legend.bg_color`` (str):
|
|
78
|
+
- ``chart.legend.font_style`` (str):
|
|
79
|
+
- ``chart.legend.font_family`` (str):
|
|
80
|
+
- ``chart.legend.font_color`` (str):
|
|
81
|
+
- ``chart.legend.font_size`` (float):
|
|
82
|
+
|
|
83
|
+
**title**
|
|
84
|
+
- ``chart.title.font_style`` (str)
|
|
85
|
+
- ``chart.title.font_family`` (str)
|
|
86
|
+
- ``chart.title.font_color`` (str)
|
|
87
|
+
- ``chart.title.font_size`` (float)
|
|
88
|
+
|
|
89
|
+
**x_axis:**
|
|
90
|
+
*ticks:*
|
|
91
|
+
- ``chart.x_axis.ticks.font_size`` (float)
|
|
92
|
+
- ``chart.x_axis.ticks.font_color`` (str)
|
|
93
|
+
- ``chart.x_axis.ticks.font_family`` (str)
|
|
94
|
+
*label:*
|
|
95
|
+
- ``chart.x_axis.label.font_size`` (float)
|
|
96
|
+
- ``chart.x_axis.label.font_color`` (str)
|
|
97
|
+
- ``chart.x_axis.label.font_family`` (str)
|
|
98
|
+
|
|
99
|
+
**y_axis:**
|
|
100
|
+
*ticks:*
|
|
101
|
+
- ``chart.y_axis.ticks.font_size`` (float)
|
|
102
|
+
- ``chart.y_axis.ticks.font_color`` (str)
|
|
103
|
+
- ``chart.y_axis.ticks.font_family`` (str)
|
|
104
|
+
*label:*
|
|
105
|
+
- ``chart.y_axis.label.font_size`` (float)
|
|
106
|
+
- ``chart.y_axis.label.font_color`` (str)
|
|
107
|
+
- ``chart.y_axis.label.font_family`` (str)
|
|
108
|
+
|
|
109
|
+
**series:**
|
|
110
|
+
- ``chart.series.bar_edge_color`` (str): Border color for bars in bar charts, default black.
|
|
111
|
+
- ``chart.series.bar_edge_width`` (int): Border width for bars in bar charts.
|
|
112
|
+
- ``chart.series.bar_face_color`` (str): Fill color for bars in bar charts, default green.
|
|
113
|
+
- ``chart.series.line_width`` (int): Line width, default 1.
|
|
114
|
+
- ``chart.series.line_style`` (str): 'solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot'.
|
|
115
|
+
- ``chart.series.line_color`` (str): Color of the line.
|
|
116
|
+
- ``chart.series.marker_color`` (str): Color of the marker symbol.
|
|
117
|
+
- ``chart.series.marker_size`` (int): Size of the marker symbol.
|
|
118
|
+
- ``chart.series.marker_width`` (int): Marker width default is 0; some, like asterisks, require a width of 1.
|
|
119
|
+
|
|
120
|
+
**Report:**
|
|
121
|
+
- ``report.font_style`` (str)
|
|
122
|
+
- ``report.font_family`` (str)
|
|
123
|
+
- ``report.font_color`` (str)
|
|
124
|
+
- ``report.font_size`` (float)
|
|
125
|
+
- ``report.page_margin_left`` (int)
|
|
126
|
+
- ``report.page_margin_top`` (int)
|
|
127
|
+
|
|
128
|
+
**Text:**
|
|
129
|
+
- ``text.font_style`` (str): as string: "B", "I", "BI"
|
|
130
|
+
- ``text.font_family`` (str)
|
|
131
|
+
- ``text.font_size`` (float)
|
|
132
|
+
|
|
133
|
+
**Chapter:**
|
|
134
|
+
- ``chapter.font_style`` (str)
|
|
135
|
+
- ``chapter.font_family`` (str)
|
|
136
|
+
- ``chapter.font_size`` (float)
|
|
137
|
+
|
|
138
|
+
:key kwargs (any): You can provide any key-value pair to customize settings. If Object A contains Object B, settings for Object B can be defined in Object A and will apply to all instances of Object B, unless overridden for a specific instance
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
self.CHILDREN = []
|
|
142
|
+
self.title = title
|
|
143
|
+
self.subtitle = subtitle
|
|
144
|
+
self.load_fonts = get_fonts(load_fonts)
|
|
145
|
+
self.abstract = abstract
|
|
146
|
+
self.settings = SettingsContainer(**kwargs)
|
|
147
|
+
|
|
148
|
+
if hasattr(self.settings, "styles"):
|
|
149
|
+
self.settings.styles = add_prefix_to_styles("report", self.settings.styles)
|
|
150
|
+
|
|
151
|
+
merge_settings(self, self.settings.__dict__)
|
|
152
|
+
|
|
153
|
+
def add(self, report_child : Union[Grid, Table, Text, PageBreak, Chart, Chapter] = None):
|
|
154
|
+
if report_child is not None and isinstance(report_child, (Grid, Table, Text, PageBreak, Chart, Chapter)):
|
|
155
|
+
copy_report_child = copy.deepcopy(report_child)
|
|
156
|
+
self.CHILDREN.append(copy_report_child)
|
|
157
|
+
merge_settings(self, self.settings.__dict__)
|
|
158
|
+
else: raise Exception("Report: report child of wrong type or None")
|
|
159
|
+
|
|
160
|
+
# TODO: Output needs to know about mapper, so regarding circularity, extracting output would help us.
|
|
161
|
+
# Should be part of report mapper
|
|
162
|
+
def output(self, file_name: str = "report", cleanup: bool = True):
|
|
163
|
+
"""
|
|
164
|
+
:args file_name (str): name of the report output file
|
|
165
|
+
:args cleanup (bool): if True, figures will be kept within /tmp directory
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
pdf = PDF(
|
|
169
|
+
orientation = self.settings.orientation,
|
|
170
|
+
unit = self.settings.unit,
|
|
171
|
+
format = self.settings.format,
|
|
172
|
+
report_styles=self.settings.styles["report"],
|
|
173
|
+
report_title=self.title,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
add_custom_fonts(pdf, self.load_fonts)
|
|
177
|
+
|
|
178
|
+
pdf.set_margins(left=self.settings.styles["report"]["page_margin_left"],
|
|
179
|
+
top=self.settings.styles["report"]["page_margin_top"])
|
|
180
|
+
|
|
181
|
+
reportMapper = ReportMapper(pdf)
|
|
182
|
+
|
|
183
|
+
reportMapper.compile(self, file_name, cleanup)
|
|
184
|
+
|
|
185
|
+
def set_front_page(self, title: str = "", subtitle: str = "", abstract: str="", logo: Optional[str] = None):
|
|
186
|
+
if bool(title):
|
|
187
|
+
self.title = title
|
|
188
|
+
if bool(subtitle):
|
|
189
|
+
self.subtitle = subtitle
|
|
190
|
+
if bool(abstract):
|
|
191
|
+
self.abstract = abstract
|
|
192
|
+
if bool(logo):
|
|
193
|
+
self.settings.logo = logo
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
rephorm/objects/table.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from typing import Optional, List, Union
|
|
3
|
+
|
|
4
|
+
from rephorm.objects.table_section import TableSection
|
|
5
|
+
from rephorm.objects.table_series import TableSeries
|
|
6
|
+
from rephorm.object_mappers.table_mapper import TableMapper
|
|
7
|
+
from rephorm.objects._utilities.settings_container import SettingsContainer
|
|
8
|
+
from rephorm.utility.add_style_prefix import add_prefix_to_styles
|
|
9
|
+
from rephorm.utility.report.range_utility import get_span, get_highlight
|
|
10
|
+
from rephorm.decorators.settings_validation import validate_kwargs
|
|
11
|
+
from rephorm.utility.report.table_utility import get_table_highlight
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Table:
|
|
15
|
+
|
|
16
|
+
@validate_kwargs
|
|
17
|
+
def __init__(self, title: str = "", footnotes: Optional[List[str]] = None, width: int = None, **kwargs):
|
|
18
|
+
"""
|
|
19
|
+
:param title: Title of the table.
|
|
20
|
+
:param footnotes: Footnotes for the title.
|
|
21
|
+
:param width (int): The maximum width allowed for rendering the table.
|
|
22
|
+
:key span (ir.Span, Required): Span of the table.
|
|
23
|
+
:key highlight (ir.Span): Span of the highlight.
|
|
24
|
+
:key show_units (bool): Display/hide units in the table.
|
|
25
|
+
:key frequency (str): Frequency of the data in the table.
|
|
26
|
+
:key decimal_precision (int): Sets number of decimal places after the decimal point.
|
|
27
|
+
:key compare_style (str): Comparison style for the table series. "diff", "pct".
|
|
28
|
+
:key layout (Dict): Layout configuration for the table (Plotly specific).
|
|
29
|
+
:key styles (Dict): Styles dictionary for additional customization (for details refer to report object).
|
|
30
|
+
"""
|
|
31
|
+
self.CHILDREN = []
|
|
32
|
+
self.footnotes = footnotes
|
|
33
|
+
self.title = title
|
|
34
|
+
self.width = width
|
|
35
|
+
self.settings = SettingsContainer(**kwargs)
|
|
36
|
+
|
|
37
|
+
if hasattr(self.settings, "styles"):
|
|
38
|
+
self.settings.styles = add_prefix_to_styles("table", self.settings.styles)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def add(self, table_child: Union[TableSeries, TableSection] = None,):
|
|
42
|
+
|
|
43
|
+
self._on_add_callback()
|
|
44
|
+
|
|
45
|
+
if table_child is not None and isinstance(table_child, (TableSeries, TableSection)):
|
|
46
|
+
copy_table_child = copy.deepcopy(table_child)
|
|
47
|
+
self.CHILDREN.append(copy_table_child)
|
|
48
|
+
else:
|
|
49
|
+
raise Exception("Table: Table child of wrong type or None")
|
|
50
|
+
|
|
51
|
+
def _get_mapper(self):
|
|
52
|
+
return TableMapper(self)
|
|
53
|
+
|
|
54
|
+
def _get_span(self):
|
|
55
|
+
return get_span(self)
|
|
56
|
+
|
|
57
|
+
def _get_highlight(self):
|
|
58
|
+
return get_highlight(self)
|
|
59
|
+
|
|
60
|
+
def _get_table_highlight(self):
|
|
61
|
+
return get_table_highlight(self)
|
|
62
|
+
|
|
63
|
+
# Because sometimes we may want to print the name of the object
|
|
64
|
+
def __repr__(self):
|
|
65
|
+
return f"{type(self).__name__}"
|
|
66
|
+
|
|
67
|
+
def _on_add_callback(self):
|
|
68
|
+
|
|
69
|
+
if hasattr(self.settings, 'styles'):
|
|
70
|
+
highlight_color = self.settings.styles.get("table.highlight_color")
|
|
71
|
+
|
|
72
|
+
if highlight_color:
|
|
73
|
+
# Set default values for various highlight_color properties
|
|
74
|
+
self.settings.styles.setdefault("table.series.highlight_color", highlight_color)
|
|
75
|
+
self.settings.styles.setdefault("table.heading.highlight_color", highlight_color)
|
|
76
|
+
self.settings.styles.setdefault("table.section.highlight_color", highlight_color)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
from rephorm.object_mappers.table_section_mapper import TableSectionMapper
|
|
4
|
+
from rephorm.objects.table_series import TableSeries
|
|
5
|
+
from rephorm.objects._utilities.settings_container import SettingsContainer
|
|
6
|
+
from rephorm.decorators.settings_validation import validate_kwargs
|
|
7
|
+
from rephorm.utility.add_style_prefix import add_prefix_to_styles
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TableSection:
|
|
11
|
+
|
|
12
|
+
@validate_kwargs
|
|
13
|
+
def __init__(self, title: str = None, **kwargs):
|
|
14
|
+
"""
|
|
15
|
+
:param title: (str) Title of the table section.
|
|
16
|
+
:key styles (Dict): Styles dictionary for the table section (refer to documentation).
|
|
17
|
+
:key highlight (ir.Span): Span of the highlight in the table section. (applies to all table_series added within the section)
|
|
18
|
+
:key show_units (bool): To show/hide units for the table section. (applies to all table_series added within the section)
|
|
19
|
+
"""
|
|
20
|
+
self.TITLE = title
|
|
21
|
+
self.CHILDREN = []
|
|
22
|
+
self.settings = SettingsContainer(**kwargs)
|
|
23
|
+
if hasattr(self.settings, "styles"):
|
|
24
|
+
self.settings.styles = add_prefix_to_styles("table.section", self.settings.styles)
|
|
25
|
+
|
|
26
|
+
def add (self, table_section_child: TableSeries = None):
|
|
27
|
+
|
|
28
|
+
self._on_add_callback()
|
|
29
|
+
|
|
30
|
+
if table_section_child is not None and isinstance(table_section_child, TableSeries):
|
|
31
|
+
copy_table_section_child = copy.deepcopy(table_section_child)
|
|
32
|
+
self.CHILDREN.append(copy_table_section_child)
|
|
33
|
+
else:
|
|
34
|
+
raise Exception("TableSection: Table Section child of wrong type or None")
|
|
35
|
+
|
|
36
|
+
def _get_mapper(self):
|
|
37
|
+
return TableSectionMapper(self)
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return f"{type(self).__name__}"
|
|
41
|
+
|
|
42
|
+
def _on_add_callback(self):
|
|
43
|
+
if hasattr(self.settings, 'styles'):
|
|
44
|
+
highlight_color = self.settings.styles.get("table.section.highlight_color")
|
|
45
|
+
if highlight_color:
|
|
46
|
+
self.settings.styles.setdefault("table.series.highlight_color", highlight_color)
|
|
47
|
+
self.settings.styles.setdefault("table.section.highlight_color", highlight_color)
|
|
48
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from irispie import Series as IrisSeries
|
|
2
|
+
|
|
3
|
+
from rephorm.object_mappers.table_series_mapper import TableSeriesMapper
|
|
4
|
+
from rephorm.objects._utilities.settings_container import SettingsContainer
|
|
5
|
+
from rephorm.decorators.settings_validation import validate_kwargs
|
|
6
|
+
from rephorm.utility.add_style_prefix import add_prefix_to_styles
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TableSeries:
|
|
10
|
+
|
|
11
|
+
@validate_kwargs
|
|
12
|
+
def __init__(self, title: str = None, unit: str = None, data: IrisSeries = None, **kwargs):
|
|
13
|
+
"""
|
|
14
|
+
:param data: (IrisSeries, Required) Data for the table series.
|
|
15
|
+
:param title: (str) Name of the table series / row.
|
|
16
|
+
:param unit: (str) The text value for the "units" column in this specific row of the table series.
|
|
17
|
+
This defines what will be displayed as the unit for the corresponding row.
|
|
18
|
+
:key span (ir.Span): Span of the table series.
|
|
19
|
+
:key highlight (ir.Span): Span of the highlight in the table series.
|
|
20
|
+
:key compare_style (str): Comparison style for the table series. "diff", "pct".
|
|
21
|
+
:key decimal_precision (int): Sets number of decimal places after the decimal point.
|
|
22
|
+
:key comparison_series (bool): Whether the series should be displayed as comparison series.
|
|
23
|
+
:key styles (Dict): Styles dictionary for additional customization (for details refer to report object).
|
|
24
|
+
"""
|
|
25
|
+
self.data = data
|
|
26
|
+
self.title = title
|
|
27
|
+
self.unit = unit
|
|
28
|
+
self.settings = SettingsContainer(**kwargs)
|
|
29
|
+
if hasattr(self.settings, "styles"):
|
|
30
|
+
self.settings.styles = add_prefix_to_styles("table.series", self.settings.styles)
|
|
31
|
+
|
|
32
|
+
def _get_mapper(self):
|
|
33
|
+
return TableSeriesMapper(self)
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return f"{type(self).__name__}"
|
rephorm/objects/text.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
from rephorm.decorators.settings_validation import validate_kwargs
|
|
3
|
+
from rephorm.object_mappers.text_mapper import TextMapper
|
|
4
|
+
from rephorm.objects._utilities.settings_container import SettingsContainer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Text:
|
|
8
|
+
# ----------------------------------
|
|
9
|
+
# Class initiator
|
|
10
|
+
# ----------------------------------
|
|
11
|
+
@validate_kwargs
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
title: str = "",
|
|
15
|
+
text: str = "",
|
|
16
|
+
align: Literal["x", "c", "l", "j", "r"] = "j",
|
|
17
|
+
markdown = False,
|
|
18
|
+
**kwargs
|
|
19
|
+
):
|
|
20
|
+
self.title = title
|
|
21
|
+
self.TEXT = text
|
|
22
|
+
self.align = align
|
|
23
|
+
self.markdown = markdown
|
|
24
|
+
self.settings = SettingsContainer(**kwargs)
|
|
25
|
+
|
|
26
|
+
def _get_mapper(self):
|
|
27
|
+
return TextMapper(self)
|
|
28
|
+
|
|
29
|
+
def __repr__(self):
|
|
30
|
+
return f"{type(self).__name__}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
rephorm/utility/PDF.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from fpdf import FPDF
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
This subclass extends FPDF to include customized header and footer functionality,
|
|
7
|
+
making it easier to apply consistent formatting across all pages of the document.
|
|
8
|
+
By overriding the `header` and `footer` methods, we ensure that these elements
|
|
9
|
+
are automatically added to each page without manual intervention.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
class PDF(FPDF):
|
|
13
|
+
def __init__(self, report_styles = None, report_title = None, *args, **kwargs):
|
|
14
|
+
super().__init__(*args, **kwargs)
|
|
15
|
+
self.report_styles = report_styles
|
|
16
|
+
self.report_title = report_title
|
|
17
|
+
self.timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
18
|
+
|
|
19
|
+
def header(self):
|
|
20
|
+
header_height = self.report_styles["header"]["height"]
|
|
21
|
+
self.set_font(family=self.report_styles["font_family"], size=8)
|
|
22
|
+
# Todo: here, H should be set to header height
|
|
23
|
+
# Set absolute position for the header
|
|
24
|
+
# We need to skip first cell (bcs of logo).
|
|
25
|
+
header_width = self.epw / 3
|
|
26
|
+
y = self.get_y()
|
|
27
|
+
|
|
28
|
+
layout = self._get_header_layout(self.page_no(), self.timestamp, self.report_title)
|
|
29
|
+
|
|
30
|
+
for item in layout:
|
|
31
|
+
x = self.l_margin + item["slot"] * header_width
|
|
32
|
+
self.set_xy(x, y)
|
|
33
|
+
self.cell(
|
|
34
|
+
w=header_width,
|
|
35
|
+
h=header_height,
|
|
36
|
+
text=item["text"],
|
|
37
|
+
align=item.get("align", "L"),
|
|
38
|
+
# border=1 #for debugging
|
|
39
|
+
)
|
|
40
|
+
# Todo: was easier to extract it like this, and in the future we could rewr this function
|
|
41
|
+
# further in order to specify exact positions of elements.
|
|
42
|
+
def _get_header_layout(self, page_no, timestamp, report_title):
|
|
43
|
+
layout = [
|
|
44
|
+
{
|
|
45
|
+
"id": "title",
|
|
46
|
+
"text": report_title,
|
|
47
|
+
"align": "L",
|
|
48
|
+
"slot": 0
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "page",
|
|
52
|
+
"text": f"Page {page_no}",
|
|
53
|
+
"align": "C",
|
|
54
|
+
"slot": 1
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "timestamp",
|
|
58
|
+
"text": timestamp,
|
|
59
|
+
"align": "R",
|
|
60
|
+
"slot": 2
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
# Todo: modify this, once we have show_title_page option on report object that controls whether the
|
|
65
|
+
# title page shown or not.
|
|
66
|
+
if page_no == 1:
|
|
67
|
+
layout = [{
|
|
68
|
+
"id": "timestamp",
|
|
69
|
+
"text": timestamp,
|
|
70
|
+
"align": "R",
|
|
71
|
+
"slot": 2
|
|
72
|
+
}]
|
|
73
|
+
|
|
74
|
+
return layout
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# The 'prefixes' tuple contains the current object name prefixes.
|
|
2
|
+
# When defining styles, avoid using object names as keys.
|
|
3
|
+
# For example, a structure like Grid: { chart: { } }
|
|
4
|
+
# will cause issues, because chart is also an object name.
|
|
5
|
+
|
|
6
|
+
prefixes = (
|
|
7
|
+
"chart.",
|
|
8
|
+
"grid.",
|
|
9
|
+
"report.",
|
|
10
|
+
"chapter.",
|
|
11
|
+
"table.",
|
|
12
|
+
"text.",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def flatten_dict(d, parent_key='', sep='.'):
|
|
16
|
+
items = []
|
|
17
|
+
for k, v in d.items():
|
|
18
|
+
new_key = f"{parent_key}{sep}{k}" if parent_key else k
|
|
19
|
+
if isinstance(v, dict):
|
|
20
|
+
items.extend(flatten_dict(v, new_key, sep=sep).items())
|
|
21
|
+
else:
|
|
22
|
+
items.append((new_key, v))
|
|
23
|
+
return dict(items)
|
|
24
|
+
|
|
25
|
+
def add_prefix_to_styles(obj_name, styles):
|
|
26
|
+
flatten_styles = flatten_dict(styles)
|
|
27
|
+
return {
|
|
28
|
+
key if key.startswith(prefixes) else f"{obj_name}.{key}": value
|
|
29
|
+
for key, value in flatten_styles.items()
|
|
30
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|