__init__.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. """
  2. __init__.py
  3. The functions in this module are meant for plotting the histogram objects created via
  4. matplotboard.histogram
  5. """
  6. import re
  7. from io import BytesIO
  8. from base64 import b64encode
  9. from markdown import Markdown
  10. import latexipy as lp
  11. __all__ = ['decl_fig',
  12. 'render',
  13. 'generate_report']
  14. MD = Markdown(extensions=['mdx_math', 'tables'],
  15. extension_configs={'mdx_math': {'enable_dollar_delimiter': True}})
  16. lp.latexify(params={'pgf.texsystem': 'pdflatex',
  17. 'text.usetex': True,
  18. 'font.family': 'serif',
  19. 'pgf.preamble': [],
  20. 'font.size': 15,
  21. 'axes.labelsize': 15,
  22. 'axes.titlesize': 13,
  23. 'legend.fontsize': 13,
  24. 'xtick.labelsize': 11,
  25. 'ytick.labelsize': 11,
  26. 'figure.dpi': 150,
  27. 'savefig.transparent': False,
  28. },
  29. new_backend='TkAgg')
  30. def decl_fig(fn):
  31. from functools import wraps
  32. def _fn_call_to_dict(fn, *args, **kwargs):
  33. from inspect import signature
  34. from html import escape
  35. pnames = list(signature(fn).parameters)
  36. pvals = list(args) + list(kwargs.values())
  37. return {escape(str(k)): escape(str(v)) for k, v in zip(pnames, pvals)}
  38. def _process_docs(fn):
  39. from inspect import getdoc
  40. raw = getdoc(fn)
  41. if raw:
  42. return MD.convert(raw)
  43. else:
  44. return None
  45. @wraps(fn)
  46. def f(*args, **kwargs):
  47. global _decl_counter
  48. txt = fn(*args, **kwargs)
  49. argdict = _fn_call_to_dict(fn, *args, **kwargs)
  50. docs = _process_docs(fn)
  51. if not txt:
  52. txt = ''
  53. html = MD.convert(txt)
  54. return argdict, docs, html
  55. return f
  56. def render(figures, scale=1.0):
  57. from namedlist import namedlist
  58. Figure = namedlist('Figure', 'name data argdict docs html idx')
  59. def exec_fig(fig):
  60. if not isinstance(fig, tuple):
  61. fn, args, kwargs = fig, (), {}
  62. elif len(fig) == 1:
  63. fn, args, kwargs = fig[0], (), {}
  64. elif len(fig) == 2:
  65. fn, args, kwargs = fig[0], fig[1], {}
  66. elif len(fig) == 3:
  67. fn, args, kwargs = fig[0], fig[1], fig[2]
  68. else:
  69. raise ValueError('Plot tuple must be of format (func), '
  70. f'or (func, tuple), or (func, tuple, dict). Got {fig}')
  71. return fn(*args, **kwargs)
  72. for idx, (name, figure) in enumerate(figures.items()):
  73. print(f'Building plot #{idx}: {name}')
  74. out = BytesIO()
  75. with lp.mem_figure(out,
  76. ext='png',
  77. size=(scale * 10, scale * 10)):
  78. argdict, docs, html = exec_fig(figure)
  79. out.seek(0)
  80. figures[name] = Figure(name, out, argdict, docs, html, idx)
  81. def generate_report(figures, title, outputdir='report',
  82. source=None, ana_source=None, config=None, body=None):
  83. from os.path import join, dirname, abspath
  84. from os import mkdir
  85. from shutil import rmtree, copytree
  86. from jinja2 import Environment, PackageLoader, select_autoescape, Template
  87. from urllib.parse import quote
  88. if body is None:
  89. raise ValueError("You must supply the body of the report!")
  90. env = Environment(
  91. loader=PackageLoader('matplotboard', 'templates'),
  92. autoescape=select_autoescape(['htm', 'html', 'xml']),
  93. )
  94. env.globals.update({'quote': quote,
  95. 'enumerate': enumerate,
  96. 'zip': zip,
  97. })
  98. if source is not None:
  99. with open(source, 'r') as f:
  100. source = f.read()
  101. rmtree(outputdir)
  102. mkdir(outputdir)
  103. pkgdir = dirname(abspath(__file__))
  104. copytree(join(pkgdir, 'static', 'js'), join(outputdir, 'js'))
  105. copytree(join(pkgdir, 'static', 'css'), join(outputdir, 'css'))
  106. figure_dir = join(outputdir, 'figures')
  107. mkdir(figure_dir)
  108. for name, figure in figures.items():
  109. fname = join(figure_dir, f'{figure.name}.png')
  110. with open(fname, 'wb') as f:
  111. f.write(figure.data.read())
  112. body = re.sub(r'fig::(\w+)', r'{{ fig(figures["\1"]) }}', body)
  113. body = MD.convert(body)
  114. report_template = env.from_string(f'''
  115. {{% extends("report.j2")%}}
  116. {{% from 'macros.j2' import fig %}}
  117. {{% block body %}}
  118. {body}
  119. {{% endblock %}}''')
  120. with open(join(outputdir, 'report.html'), 'w') as f:
  121. f.write(report_template.render(
  122. title=title,
  123. figures=figures,
  124. source=source,
  125. ana_source=ana_source,
  126. config=config,
  127. ))
  128. # def hists_to_table(hists, row_labels=(), column_labels=(), format="{:.2f}"):
  129. # table = ['<table class="table table-condensed">']
  130. # if column_labels:
  131. # table.append('<thead><tr>')
  132. # if row_labels:
  133. # table.append('<th></th>')
  134. # table.extend(f'<th>{label}</th>' for label in column_labels)
  135. # table.append('</tr></thead>')
  136. # table.append('<tbody>\n')
  137. # for row_label, (vals, *_) in zip_longest(row_labels, hists):
  138. # table.append('<tr>')
  139. # if row_label:
  140. # table.append(f'<td><strong>{row_label}</strong></td>')
  141. # table.extend(('<td>'+format.format(val)+'</td>') for val in vals)
  142. # table.append('</tr>\n')
  143. # table.append('</tbody></table>')
  144. # return ''.join(table)