From c489ce7d1ea2b7285d8c7503747dfbe26a3c0cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Tue, 16 May 2017 11:00:50 +0200 Subject: [PATCH] Initial commit. --- .gitignore | 2 ++ pyltk/__init__.py | 10 +++++++ pyltk/figure.py | 34 ++++++++++++++++++++++ pyltk/formatter.py | 13 +++++++++ pyltk/makebox.py | 41 ++++++++++++++++++++++++++ pyltk/resizebox.py | 38 ++++++++++++++++++++++++ pyltk/subfloat.py | 23 +++++++++++++++ pyltk/tabular.py | 53 ++++++++++++++++++++++++++++++++++ pyltk/tikz/__init__.py | 6 ++++ pyltk/tikz/axis.py | 45 +++++++++++++++++++++++++++++ pyltk/tikz/coordinates_plot.py | 15 ++++++++++ pyltk/tikz/errorbars_plot.py | 24 +++++++++++++++ pyltk/tikz/generic_plot.py | 35 ++++++++++++++++++++++ pyltk/tikz/plots.py | 7 +++++ pyltk/tikz/tikzpicture.py | 29 +++++++++++++++++++ tests/test_basic.py | 50 ++++++++++++++++++++++++++++++++ 16 files changed, 425 insertions(+) create mode 100644 .gitignore create mode 100644 pyltk/__init__.py create mode 100644 pyltk/figure.py create mode 100644 pyltk/formatter.py create mode 100644 pyltk/makebox.py create mode 100644 pyltk/resizebox.py create mode 100644 pyltk/subfloat.py create mode 100644 pyltk/tabular.py create mode 100644 pyltk/tikz/__init__.py create mode 100644 pyltk/tikz/axis.py create mode 100644 pyltk/tikz/coordinates_plot.py create mode 100644 pyltk/tikz/errorbars_plot.py create mode 100644 pyltk/tikz/generic_plot.py create mode 100644 pyltk/tikz/plots.py create mode 100644 pyltk/tikz/tikzpicture.py create mode 100644 tests/test_basic.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96c3ff7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +**/__pycache__ diff --git a/pyltk/__init__.py b/pyltk/__init__.py new file mode 100644 index 0000000..3a0772e --- /dev/null +++ b/pyltk/__init__.py @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- + +# flake8: noqa + +from .figure import figure +from .formatter import formatter +from .makebox import makebox +from .resizebox import resizebox +from .subfloat import subfloat +from .tabular import tabular diff --git a/pyltk/figure.py b/pyltk/figure.py new file mode 100644 index 0000000..1bc3761 --- /dev/null +++ b/pyltk/figure.py @@ -0,0 +1,34 @@ +# -*- encoding: utf-8 -*- + +from .formatter import formatter + + +class figure: + """ Class representing a latex figure. """ + + fmt = formatter({ + 'content': """\\begin{{figure}}[{options}] +{centered} +{inner} +\\end{{figure}}""" + }) + + def __init__(self, inner, options='!h', centered=True): + """ Create a figure with given options. + + Parameters: + - options Options for the figure. + - centered Set to true to add a centering command. + """ + self.inner = inner + self.options = options + self.centered = '' + if centered: + self.centered = '\\centering' + + def __str__(self): + return self.fmt.format('content', { + 'options': self.options, + 'centered': self.centered, + 'inner': self.inner + }) diff --git a/pyltk/formatter.py b/pyltk/formatter.py new file mode 100644 index 0000000..b8cea7e --- /dev/null +++ b/pyltk/formatter.py @@ -0,0 +1,13 @@ +# -*- encoding: utf-8 -*- + + +class formatter: + + """ Simple formatter class. """ + + def __init__(self, templates): + self.templates = templates + + def format(self, key, data): + """ Format the internal state. """ + return self.templates[key].format(**data) diff --git a/pyltk/makebox.py b/pyltk/makebox.py new file mode 100644 index 0000000..7301c4e --- /dev/null +++ b/pyltk/makebox.py @@ -0,0 +1,41 @@ +# -*- encoding: utf-8 -*- + +from .formatter import formatter + + +class makebox: + """ Class representing an unindented makebox. """ + + fmt = formatter({ + 'content': """{noindent}\\makebox[{width}]{{ +{inner} +}}""" + }) + + def __init__(self, inner, width=None, noindent=True): + """ Create a new makebox with given width. + + Parameters: + - width Width of the box (string, float, or None). If float, + represent a percentage of \\textwidth. None is used to have + \\textwidth. + - noindent Add a \\noindent command before the box. + """ + self.inner = inner + + self.width = width + if self.width is None: + self.width = '\\textwidth' + elif type(self.width) != 'str': + self.width = '{}\\textwidth'.format(self.width) + + self.noindent = '' + if self.noindent: + self.noindent = '\\noindent' + + def __str__(self): + return self.fmt.format('content', { + 'inner': self.inner, + 'width': self.width, + 'noindent': self.noindent + }) diff --git a/pyltk/resizebox.py b/pyltk/resizebox.py new file mode 100644 index 0000000..1af95f1 --- /dev/null +++ b/pyltk/resizebox.py @@ -0,0 +1,38 @@ +# -*- encoding: utf-8 -*- + +from .formatter import formatter + + +class resizebox: + """ Class representing a latex resizebox. """ + + fmt = formatter({ + 'content': """\\resizebox{{{width}}}{{{height}}}{{ +{inner} +}}""" + }) + + def __init__(self, inner, width, height="!"): + """ Create a resizebox with given width and height. + + Parameters: + - width Width of the box. + - height Height of the box. + + The width / height parameters work differently depending on their + types: + - str Use as it is (e.g. "0.5\\linewidth", "!"). + - others Use as a percentage of linewidth (e.g. 0.5). + """ + self.inner = inner + self.width = width + if type(width) != 'str': + self.width = '{}\\linewidth'.format(width) + self.height = height + + def __str__(self): + return self.fmt.format('content', { + 'width': self.width, + 'height': self.height, + 'inner': self.inner + }) diff --git a/pyltk/subfloat.py b/pyltk/subfloat.py new file mode 100644 index 0000000..3f60d42 --- /dev/null +++ b/pyltk/subfloat.py @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- + +from .formatter import formatter + + +class subfloat: + """ Class representing a latex subfloat. """ + + fmt = formatter({ + 'content': """\\subfloat[{caption}]{{ +{inner} +}}""" + }) + + def __init__(self, inner, caption): + self.caption = caption + self.inner = inner + + def __str__(self): + return self.fmt.format('content', { + 'caption': self.caption, + 'inner': self.inner + }) diff --git a/pyltk/tabular.py b/pyltk/tabular.py new file mode 100644 index 0000000..0d0e60b --- /dev/null +++ b/pyltk/tabular.py @@ -0,0 +1,53 @@ +# -*- encoding: utf-8 -*- + +from .formatter import formatter + + +class tabular: + """ Class representing a latex tabular. """ + + fmt = formatter({ + 'content': """\\begin{{tabular}}{{{columns}}} +{inner} +\\end{{tabular}}""" + }) + + def __init__(self, rows, columns=None): + """ Create a tabular with given rows and column specification. + + Parameters: + - rows Rows for the tabular (list of list or list of string). + - columns String representing columns. + + If `rows` is a str, an empty tabular is created and `rows` is used + instead of `columns` for the columns specification. + """ + if type(rows) == str: + columns = rows + rows = [] + self.columns = columns + self.rows = [] + for row in rows: + self.addrow(row) + + def addrow(self, row): + """ Add a row to the tabular. + + Parameters: + - row Array or string (array will be joined with ' & '). + """ + if type(row) != 'str': + row = ' & '.join(map(str, row)) + self.rows.append(row) + + def addrows(self, rows): + """ Add multiple rows to the tabular (see addrow). """ + for row in rows: + self.addrow(row) + + def __str__(self): + inner = '\\\\\n'.join(self.rows) + return self.fmt.format('content', { + 'columns': self.columns, + 'inner': inner + }) diff --git a/pyltk/tikz/__init__.py b/pyltk/tikz/__init__.py new file mode 100644 index 0000000..7fc2d40 --- /dev/null +++ b/pyltk/tikz/__init__.py @@ -0,0 +1,6 @@ +# -*- encoding: utf-8 -*- + +# flake8: noqa + +from .plots import * +from .tikzpicture import tikzpicture diff --git a/pyltk/tikz/axis.py b/pyltk/tikz/axis.py new file mode 100644 index 0000000..ff8dcd0 --- /dev/null +++ b/pyltk/tikz/axis.py @@ -0,0 +1,45 @@ +# -*- encoding: utf-8 -*- + +from ..formatter import formatter + + +class axis: + """ Class representing an axis. """ + + fmt = formatter({ + 'options': '[{content}]', + 'legends': '\\legend{{{content}}}', + 'content': """\\begin{{axis}}{options} +{inner} +{legend} +\\end{{axis}}""" + }) + + def __init__(self, *args, **kargs): + self.options = kargs + self.plots = [] + for arg in args: + self.addplot(arg) + + def addplot(self, pl): + self.plots.append(pl) + + def format_options(self, options): + if not options: + return '' + opts = [] + for k, v in options.items(): + opts.append('{key} = {{{value}}}'.format(key=k, value=v)) + return self.fmt.format('options', { + 'content': ', '.join(opts) + }) + + def __str__(self): + return self.fmt.format('content', { + 'options': self.format_options(self.options), + 'inner': '\n'.join(map(str, self.plots)), + 'legend': self.fmt.format('legends', { + 'content': ', '.join('{{{}}}'.format(p.legend) + for p in self.plots) + }) + }) diff --git a/pyltk/tikz/coordinates_plot.py b/pyltk/tikz/coordinates_plot.py new file mode 100644 index 0000000..281237a --- /dev/null +++ b/pyltk/tikz/coordinates_plot.py @@ -0,0 +1,15 @@ +# -*- encoding: utf-8 -*- + +from .generic_plot import generic_plot + + +class coordinates_plot(generic_plot): + + def __init__(self, *args, **kargs): + super().__init__(*args, **kargs) + + def format_data(self, data): + pts = [] + for d in data: + pts.append('({}, {})'.format(*d)) + return 'coordinates {{{}}}'.format(''.join(pts)) diff --git a/pyltk/tikz/errorbars_plot.py b/pyltk/tikz/errorbars_plot.py new file mode 100644 index 0000000..bd319a3 --- /dev/null +++ b/pyltk/tikz/errorbars_plot.py @@ -0,0 +1,24 @@ +# -*- encoding: utf-8 -*- + +from .generic_plot import generic_plot + + +class errorbars_plot(generic_plot): + + def __init__(self, legend, data, options=[]): + options = options + [ + 'error bars/.cd', ('y dir', 'both'), ('y explicit')] + super().__init__(legend, data, options) + + def format_data(self, data): + rows = [] + for d in data: + x, y = d[:2] + try: + p, n = d[2] + except TypeError: + p = d[2] + n = p + rows.append('({x}, {y}) += (0, {p}) -= (0, {n})'.format( + x=x, y=y, p=p, n=n)) + return 'coordinates {{{}}}'.format('\n'.join(rows)) diff --git a/pyltk/tikz/generic_plot.py b/pyltk/tikz/generic_plot.py new file mode 100644 index 0000000..a41d733 --- /dev/null +++ b/pyltk/tikz/generic_plot.py @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- + +from ..formatter import formatter + + +class generic_plot: + """ Class representing a pgfplots plot. """ + + fmt = formatter({ + 'options': '+[{content}]', + 'content': """\\addplot{options} {data};""" + }) + + def __init__(self, legend, data, options=[]): + self.legend = legend + self.data = data + self.options = options + + def format_options(self, options): + if not options: + return '' + opts = [] + for opt in options: + if type(opt) is not str: + opt = '{}={}'.format(*opt) + opts.append(str(opt)) + return self.fmt.format('options', { + 'content': ', '.join(opts) + }) + + def __str__(self): + return self.fmt.format('content', { + 'data': self.format_data(self.data), + 'options': self.format_options(self.options) + }) diff --git a/pyltk/tikz/plots.py b/pyltk/tikz/plots.py new file mode 100644 index 0000000..113d38b --- /dev/null +++ b/pyltk/tikz/plots.py @@ -0,0 +1,7 @@ +# -*- encoding: utf-8 -*- + +# flake8: noqa + +from .generic_plot import generic_plot +from .coordinates_plot import coordinates_plot +from .errorbars_plot import errorbars_plot diff --git a/pyltk/tikz/tikzpicture.py b/pyltk/tikz/tikzpicture.py new file mode 100644 index 0000000..9e7ee8e --- /dev/null +++ b/pyltk/tikz/tikzpicture.py @@ -0,0 +1,29 @@ +# -*- encoding: utf-8 + +from ..formatter import formatter + +from .axis import axis + + +class tikzpicture: + """ Class representing a latex tikzfigure. """ + + fmt = formatter({ + 'content': """\\begin{{tikzpicture}} +{inner} +\\end{{tikzpicture}}""" + }) + + def __init__(self, axis=[]): + self.axis = [] + + def addaxis(self, *args, **kargs): + ax = args[0] + if not isinstance(ax, axis): + ax = axis(*args, **kargs) + self.axis.append(ax) + + def __str__(self): + return self.fmt.format('content', { + 'inner': '\n'.join(map(str, self.axis)) + }) diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100644 index 0000000..c3b6aa9 --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,50 @@ +# -*- encoding: utf-8 -*- + +import sys + +sys.path += ['..'] + +from pyltk import subfloat, tabular, resizebox, makebox, figure +from pyltk.tikz import tikzpicture, errorbars_plot, coordinates_plot + +if __name__ == "__main__": + data = [[1.00000000e+00, + 4.57213083e+00], + [2.00000000e+00, + 1.16046200e+01], + [ 4.00000000e+00, + 2.71873880e+01], + [ 5.00000000e+00, + 3.36039380e+01], + [ 1.00000000e+01, + 7.21689923e+01], + [ 2.00000000e+01, + 1.50422028e+02]] + + tk = tikzpicture() + tk.addaxis(coordinates_plot('test', data), + xlabel='', ylabel='Computation Time [s]') + + tk2 = tikzpicture() + tk2.addaxis(errorbars_plot('test 2', [ + [0, 5, (2, 3)], + [1, 8, (4, 5)] + ])) + + tk3 = tikzpicture() + tk3.addaxis(errorbars_plot('test 2', [ + [0, 5, 3], + [4, 8, 4] + ])) + + s1 = subfloat(tk, 'Caption 1') + s2 = subfloat(tk2, 'Caption 2') + s3 = subfloat(tk3, 'Caption 3') + s4 = subfloat('', 'Caption 4') + + tab = tabular([ + [resizebox(s1, 0.5), resizebox(s2, 0.5)], + [resizebox(s3, 0.5), resizebox(s4, 0.5)] + ], columns='cc') + + print(figure(makebox(tab), '!h'))