This commit is contained in:
Mikaël Capelle 2017-05-19 17:43:16 +02:00
parent c489ce7d1e
commit 6599ccb39f
23 changed files with 563 additions and 150 deletions

View File

@ -2,9 +2,13 @@
# flake8: noqa # flake8: noqa
from .documentclass import *
from .figure import figure from .figure import figure
from .formatter import formatter from .formatter import formatter
from .input_element import input_element
from .makebox import makebox from .makebox import makebox
from .resizebox import resizebox from .resizebox import resizebox
from .subfloat import subfloat from .subfloat import subfloat
from .table import table, tabledf
from .tabular import tabular from .tabular import tabular
from .inlines import it, bf, mt

88
pyltk/documentclass.py Normal file
View File

@ -0,0 +1,88 @@
# -*- encoding: utf-8 -*-
from collections import Iterable
from .element import element
import subprocess
class documentclass(element):
packages = {}
preamble = []
options = []
classname = 'standalone'
templates = {
'package': '\\usepackage{pkgoptions}{{{pkgname}}}',
'options': '[{content}]',
'def': '\\def\\{name}#{nargs}{{{value}}}',
'content': """\\documentclass{options}{{{classname}}}
{packages}
{preamble}
\\begin{{document}}
{content}
\\end{{document}}
"""
}
def __init__(self, classname, childrens=[], options=[], packages=[]):
super().__init__(parent=None, childrens=childrens)
self.options = options
self.classname = classname
self.add_packages(packages)
def add_packages(self, packages):
if type(packages) is str:
self.packages[packages] = True
elif type(packages) is dict:
self.packages.update(packages)
else:
self.packages.update({p: True for p in packages})
def add2preamble(self, value):
if isinstance(value, Iterable):
self.preamble.extend(value)
else:
self.preamble.append(value)
def add_def(self, name, nargs, value):
self.preamble.append(self.fmt().format('def', {
'name': name,
'nargs': nargs,
'value': value
}))
def format_preamble(self, defs):
return '\n'.join(self.preamble)
def format_packages(self, pkgs):
out = []
for pkg, opts in self.packages.items():
options = ''
if opts is not True:
options = self.format_options(opts)
out.append(self.fmt().format('package', {
'pkgname': pkg,
'pkgoptions': options
}))
return '\n'.join(out)
def content(self):
return self.fmt().format('content', {
'options': self.format_options(self.options),
'packages': self.format_packages(self.packages),
'preamble': self.format_preamble(self.preamble),
'classname': self.classname,
'content': super().content()
})
def compile(self, outfile, infile=None, outlog=None):
if infile is None:
infile = '/tmp/pyltk-{}.tex'.format(id(self))
with open(infile, 'w') as infd:
infd.write(self.content())
call = ['pdflatex', '-halt-on-error', '-jobname', outfile, infile]
subprocess.call(call, stdout=outlog)

85
pyltk/element.py Normal file
View File

@ -0,0 +1,85 @@
# -*- encoding: utf-8 -*-
from .formatter import formatter
from collections import Iterable
class element:
""" Class representing a latex element. """
# Joiner for childrens
sep = '\n'
# Automatically add label after element if present
autolabel = True
def __init__(self, parent=None, childrens=[], label=None):
self.parent = parent
if childrens is None:
childrens = []
if not isinstance(childrens, Iterable):
childrens = [childrens]
self.childrens = []
self.add(childrens)
self.label = label
def add(self, childrens):
if not isinstance(childrens, Iterable):
childrens = [childrens]
for child in childrens:
if not isinstance(child, element):
child = wrapper_element(child, self)
try:
child.parent.remove(child)
except:
pass
child.parent = self
self.childrens.append(child)
def content(self):
return self.sep.join(map(str, self.childrens))
def fmt(self):
return formatter(self.templates)
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 format_label(self, label):
if not label:
return ''
return '\\label{{{}}}'.format(label)
def __str__(self):
out = self.content().strip()
if self.autolabel:
out += self.format_label(self.label)
return out
class wrapper_element(element):
""" Wrapper for standard python types working as latex elements. """
escape_list = ['_', '#', '%']
def __init__(self, data, parent):
super().__init__(parent=parent)
self.data = data
def escape(self, s):
for e in self.escape_list:
s = s.replace(e, '\\' + e)
return s
def content(self):
return self.escape(str(self.data))

View File

@ -1,34 +1,46 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
from .formatter import formatter from .element import element
class figure: class figure(element):
""" Class representing a latex figure. """ """ Class representing a latex figure. """
fmt = formatter({ templates = {
'caption': '\\caption{{{content}}}',
'content': """\\begin{{figure}}[{options}] 'content': """\\begin{{figure}}[{options}]
{centered} {centered}
{inner} {inner}
{caption}{label}
\\end{{figure}}""" \\end{{figure}}"""
}) }
def __init__(self, inner, options='!h', centered=True): autolabel = False
def __init__(self, childrens=None, parent=None,
caption=None, options='!h',
centered=True, label=None):
""" Create a figure with given options. """ Create a figure with given options.
Parameters: Parameters:
- options Options for the figure. - options Options for the figure.
- centered Set to true to add a centering command. - centered Set to true to add a centering command.
""" """
self.inner = inner super().__init__(parent, childrens, label=label)
self.options = options self.options = options
self.caption = caption
self.centered = '' self.centered = ''
if centered: if centered:
self.centered = '\\centering' self.centered = '\\centering'
def __str__(self): def content(self):
return self.fmt.format('content', { caption = ''
if self.caption:
caption = self.fmt().format('caption', {'content': self.caption})
return self.fmt().format('content', {
'options': self.options, 'options': self.options,
'centered': self.centered, 'centered': self.centered,
'inner': self.inner 'caption': caption,
'label': self.format_label(self.label),
'inner': super().content()
}) })

33
pyltk/inlines.py Normal file
View File

@ -0,0 +1,33 @@
# -*- encoding: utf-8 -*-
from .element import element
class inline_element(element):
""" Class representing simple latex inline elements such as bf,
it, math. """
def __init__(self, content, parent=None):
super().__init__(parent=parent)
self.value = content
self.templates = {
'wrapper': self.wrapper
}
def content(self):
return self.fmt().format('wrapper', {
'value': self.value
})
class it(inline_element):
wrapper = '\\textit{{{value}}}'
class bf(inline_element):
wrapper = '\\textbf{{{value}}}'
class mt(inline_element):
wrapper = '${value}$'

19
pyltk/input_element.py Normal file
View File

@ -0,0 +1,19 @@
# -*- encoding: utf-8 -*-
from .element import element
class input_element(element):
templates = {
'element': '\\input{{{filename}}}'
}
def __init__(self, filename, parent=None):
super().__init__(parent)
self.filename = filename.replace('.tex', '')
def content(self):
return self.fmt().format('element', {
'filename': self.filename
})

View File

@ -1,18 +1,18 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
from .formatter import formatter from .element import element
class makebox: class makebox(element):
""" Class representing an unindented makebox. """ """ Class representing an unindented makebox. """
fmt = formatter({ templates = {
'content': """{noindent}\\makebox[{width}]{{ 'content': """{noindent}\\makebox[{width}]{{
{inner} {inner}
}}""" }}"""
}) }
def __init__(self, inner, width=None, noindent=True): def __init__(self, content=None, parent=None, width=None, noindent=True):
""" Create a new makebox with given width. """ Create a new makebox with given width.
Parameters: Parameters:
@ -21,7 +21,7 @@ class makebox:
\\textwidth. \\textwidth.
- noindent Add a \\noindent command before the box. - noindent Add a \\noindent command before the box.
""" """
self.inner = inner super().__init__(parent, content)
self.width = width self.width = width
if self.width is None: if self.width is None:
@ -33,9 +33,9 @@ class makebox:
if self.noindent: if self.noindent:
self.noindent = '\\noindent' self.noindent = '\\noindent'
def __str__(self): def content(self):
return self.fmt.format('content', { return self.fmt().format('content', {
'inner': self.inner, 'inner': super().content(),
'width': self.width, 'width': self.width,
'noindent': self.noindent 'noindent': self.noindent
}) })

View File

@ -0,0 +1,7 @@
# -*- encoding: utf-8 -*-
# flake8: noqa
from .axis import axis
from .semilogaxis import *
from .plots import *

39
pyltk/pgfplots/axis.py Normal file
View File

@ -0,0 +1,39 @@
# -*- encoding: utf-8 -*-
from ..element import element
class axis(element):
""" Class representing an axis. """
templates = {
'options': '[{content}]',
'legends': '\\legend{{{content}}}',
'content': """\\begin{{axis}}{options}
{inner}
{legend}
\\end{{axis}}"""
}
def __init__(self, *args, parent=None, legend=True, label=None, **kargs):
super().__init__(parent, label=label)
self.options = kargs
self.legend = legend
for arg in args:
self.add(arg)
def content(self):
leg = ''
if self.legend:
if type(self.legend) is str:
leg = self.legend
else:
leg = self.fmt().format('legends', {
'content': ', '.join('{{{}}}'.format(p.legend)
for p in self.childrens)
})
return self.fmt().format('content', {
'options': self.format_options(self.options),
'inner': super().content(),
'legend': leg
})

View File

@ -5,10 +5,10 @@ from .generic_plot import generic_plot
class errorbars_plot(generic_plot): class errorbars_plot(generic_plot):
def __init__(self, legend, data, options=[]): def __init__(self, legend, data, label=None, options=[]):
options = options + [ options = options + [
'error bars/.cd', ('y dir', 'both'), ('y explicit')] 'error bars/.cd', ('y dir', 'both'), ('y explicit')]
super().__init__(legend, data, options) super().__init__(legend, data, label=label, options=options)
def format_data(self, data): def format_data(self, data):
rows = [] rows = []

View File

@ -1,17 +1,18 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
from ..formatter import formatter from ..element import element
class generic_plot: class generic_plot(element):
""" Class representing a pgfplots plot. """ """ Class representing a pgfplots plot. """
fmt = formatter({ templates = {
'options': '+[{content}]', 'options': '+[{content}]',
'content': """\\addplot{options} {data};""" 'content': """\\addplot{options} {data};"""
}) }
def __init__(self, legend, data, options=[]): def __init__(self, legend, data, label=None, options=[]):
super().__init__(label=label)
self.legend = legend self.legend = legend
self.data = data self.data = data
self.options = options self.options = options
@ -24,12 +25,12 @@ class generic_plot:
if type(opt) is not str: if type(opt) is not str:
opt = '{}={}'.format(*opt) opt = '{}={}'.format(*opt)
opts.append(str(opt)) opts.append(str(opt))
return self.fmt.format('options', { return self.fmt().format('options', {
'content': ', '.join(opts) 'content': ', '.join(opts)
}) })
def __str__(self): def content(self):
return self.fmt.format('content', { return self.fmt().format('content', {
'data': self.format_data(self.data), 'data': self.format_data(self.data),
'options': self.format_options(self.options) 'options': self.format_options(self.options)
}) })

View File

@ -0,0 +1,35 @@
# -*- encoding: utf-8 -*-
from .axis import axis
class semilogyaxis(axis):
""" Class representing an axis. """
templates = {
'options': '[{content}]',
'legends': '\\legend{{{content}}}',
'content': """\\begin{{semilogyaxis}}{options}
{inner}
{legend}
\\end{{semilogyaxis}}"""
}
def __init__(self, *args, **kargs):
super().__init__(*args, **kargs)
class semilogxaxis(axis):
""" Class representing an axis. """
templates = {
'options': '[{content}]',
'legends': '\\legend{{{content}}}',
'content': """\\begin{{semilogxaxis}}{options}
{inner}
{legend}
\\end{{semilogxaxis}}"""
}
def __init__(self, *args, **kargs):
super().__init__(*args, **kargs)

View File

@ -1,18 +1,19 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
from .formatter import formatter from .element import element
class resizebox: class resizebox(element):
""" Class representing a latex resizebox. """ """ Class representing a latex resizebox. """
fmt = formatter({ templates = {
'content': """\\resizebox{{{width}}}{{{height}}}{{ 'content': """\\resizebox{{{width}}}{{{height}}}{{
{inner} {inner}
}}""" }}"""
}) }
def __init__(self, inner, width, height="!"): def __init__(self, content=None, parent=None,
width="\\linewidth", height="!"):
""" Create a resizebox with given width and height. """ Create a resizebox with given width and height.
Parameters: Parameters:
@ -24,15 +25,15 @@ class resizebox:
- str Use as it is (e.g. "0.5\\linewidth", "!"). - str Use as it is (e.g. "0.5\\linewidth", "!").
- others Use as a percentage of linewidth (e.g. 0.5). - others Use as a percentage of linewidth (e.g. 0.5).
""" """
self.inner = inner super().__init__(parent, content)
self.width = width self.width = width
if type(width) != 'str': if type(width) is not str:
self.width = '{}\\linewidth'.format(width) self.width = '{}\\linewidth'.format(width)
self.height = height self.height = height
def __str__(self): def content(self):
return self.fmt.format('content', { return self.fmt().format('content', {
'width': self.width, 'width': self.width,
'height': self.height, 'height': self.height,
'inner': self.inner 'inner': super().content()
}) })

12
pyltk/strelement.py Normal file
View File

@ -0,0 +1,12 @@
# -*- encoding: utf-8 -*-
from .element import element
def strelement(element):
def __init__(self, content, parent=None):
self.inner = content
def __str__(self):
return str(self.inner)

View File

@ -1,23 +1,23 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
from .formatter import formatter from .element import element
class subfloat: class subfloat(element):
""" Class representing a latex subfloat. """ """ Class representing a latex subfloat. """
fmt = formatter({ templates = {
'content': """\\subfloat[{caption}]{{ 'content': """\\subfloat[{caption}]{{
{inner} {inner}
}}""" }}"""
}) }
def __init__(self, inner, caption): def __init__(self, caption, parent=None, childrens=None, label=None):
super().__init__(parent, childrens, label=label)
self.caption = caption self.caption = caption
self.inner = inner
def __str__(self): def content(self):
return self.fmt.format('content', { return self.fmt().format('content', {
'caption': self.caption, 'caption': self.caption,
'inner': self.inner 'inner': super().content()
}) })

60
pyltk/table.py Normal file
View File

@ -0,0 +1,60 @@
# -*- encoding: utf-8 -*-
from .element import element
from .tabular import tabular
class table(element):
""" Class representing a latex figure. """
templates = {
'caption': '\\caption{{{content}}}',
'content': """\\begin{{table}}[{options}]
{centered}
{inner}
{caption}{label}
\\end{{table}}"""
}
autolabel = False
def __init__(self, childrens=None, parent=None,
caption=None, options='!ht',
centered=True, label=None):
""" Create a table with given options.
Parameters:
- options Options for the table.
- centered Set to true to add a centering command.
"""
super().__init__(parent, childrens, label=label)
self.options = options
self.caption = caption
self.centered = ''
if centered:
self.centered = '\\centering'
def content(self):
caption = ''
if self.caption:
caption = self.fmt().format('caption', {'content': self.caption})
return self.fmt().format('content', {
'options': self.options,
'centered': self.centered,
'caption': caption,
'label': self.format_label(self.label),
'inner': super().content()
})
class tabledf(table):
def __init__(self, df, header=None, **kargs):
super().__init__(**kargs)
if header is None:
header = df.columns
tab = tabular(columns='r' * len(header))
tab.addrow(header)
tab.addhline()
tab.addrows(df.as_matrix())
self.add(tab)

View File

@ -1,53 +1,94 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
from .formatter import formatter from .element import element
class tabular: class row_element(element):
""" Class representing a row of a tabular element. """
def __init__(self, columns, parent, label=None,
wrapper=False, endline=False):
super().__init__(parent, columns, label=label)
self.wrapper = wrapper
self.endline = endline
def content(self):
wp = str
if self.wrapper:
cw = 1/len(self.childrens)
wp = lambda c: str(self.wrapper(c, width=cw))
out = ' & '.join(map(wp, self.childrens))
if self.endline:
out += '\\\\'
return out
class hline_element(row_element):
def __init__(self, parent=None):
super().__init__([], parent)
def content(self):
return '\\hline'
# instance of hline element
hline = hline_element()
class tabular(element):
""" Class representing a latex tabular. """ """ Class representing a latex tabular. """
fmt = formatter({ templates = {
'content': """\\begin{{tabular}}{{{columns}}} 'content': """\\begin{{tabular}}{{{columns}}}
{inner} {inner}
\\end{{tabular}}""" \\end{{tabular}}"""
}) }
def __init__(self, rows, columns=None): def __init__(self, parent=None, rows=[], columns=None,
autowrap=False, label=None):
""" Create a tabular with given rows and column specification. """ Create a tabular with given rows and column specification.
Parameters: Parameters:
- rows Rows for the tabular (list of list or list of string). - rows Rows for the tabular (list of list or list of string).
- columns String representing columns. - columns String representing columns options.
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: super().__init__(parent, label=label)
columns = rows
rows = []
self.columns = columns self.columns = columns
self.rows = [] self.autowrap = autowrap
for row in rows: for row in rows:
self.addrow(row) self.addrow(row)
def addrow(self, row): def addhline(self):
""" Add a hline to the current tabular element. """
self.addrow(hline_element(self), False)
def addrow(self, row, wrap=None):
""" Add a row to the tabular. """ Add a row to the tabular.
Parameters: Parameters:
- row Array or string (array will be joined with ' & '). - row Array or string (array will be joined with ' & ').
- wrap Can contains a wrapper element (typically a resizebox) for
the cell of the row.
""" """
if type(row) != 'str': if not isinstance(row, row_element):
row = ' & '.join(map(str, row)) row = row_element(row, parent=self)
self.rows.append(row) if wrap is None:
wrap = self.autowrap
row.wrapper = wrap
if self.childrens:
last = self.childrens[-1]
if isinstance(last, row_element):
last.endline = True
self.add(row)
def addrows(self, rows): def addrows(self, rows):
""" Add multiple rows to the tabular (see addrow). """ """ Add multiple rows to the tabular (see addrow). """
for row in rows: for row in rows:
self.addrow(row) self.addrow(row)
def __str__(self): def content(self):
inner = '\\\\\n'.join(self.rows) return self.fmt().format('content', {
return self.fmt.format('content', {
'columns': self.columns, 'columns': self.columns,
'inner': inner 'inner': super().content()
}) })

View File

@ -2,5 +2,4 @@
# flake8: noqa # flake8: noqa
from .plots import *
from .tikzpicture import tikzpicture from .tikzpicture import tikzpicture

View File

@ -1,45 +0,0 @@
# -*- 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)
})
})

View File

@ -1,29 +1,21 @@
# -*- encoding: utf-8 # -*- encoding: utf-8
from ..formatter import formatter from ..element import element
from .axis import axis
class tikzpicture: class tikzpicture(element):
""" Class representing a latex tikzfigure. """ """ Class representing a latex tikzfigure. """
fmt = formatter({ templates = {
'content': """\\begin{{tikzpicture}} 'content': """\\begin{{tikzpicture}}
{inner} {inner}
\\end{{tikzpicture}}""" \\end{{tikzpicture}}"""
}) }
def __init__(self, axis=[]): def __init__(self, childrens=None, parent=None, label=None):
self.axis = [] super().__init__(parent, childrens, label=label)
def addaxis(self, *args, **kargs): def content(self):
ax = args[0] return self.fmt().format('content', {
if not isinstance(ax, axis): 'inner': super().content()
ax = axis(*args, **kargs)
self.axis.append(ax)
def __str__(self):
return self.fmt.format('content', {
'inner': '\n'.join(map(str, self.axis))
}) })

View File

@ -1,13 +1,16 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
import os
import sys import sys
sys.path += ['..'] sys.path += ['..']
from pyltk import subfloat, tabular, resizebox, makebox, figure from pyltk import subfloat, tabular, resizebox, makebox, figure
from pyltk.tikz import tikzpicture, errorbars_plot, coordinates_plot from pyltk.tikz import tikzpicture
from pyltk.pgfplots import axis, errorbars_plot, coordinates_plot
if __name__ == "__main__": if __name__ == "__main__":
data = [[1.00000000e+00, data = [[1.00000000e+00,
4.57213083e+00], 4.57213083e+00],
[2.00000000e+00, [2.00000000e+00,
@ -21,30 +24,57 @@ if __name__ == "__main__":
[ 2.00000000e+01, [ 2.00000000e+01,
1.50422028e+02]] 1.50422028e+02]]
tk = tikzpicture() tk1 = tikzpicture()
tk.addaxis(coordinates_plot('test', data), tk1.add(axis(coordinates_plot('test', data),
xlabel='', ylabel='Computation Time [s]') xlabel='', ylabel='Computation Time [s]'))
tk2 = tikzpicture() tk2 = tikzpicture()
tk2.addaxis(errorbars_plot('test 2', [ tk2.add(axis(errorbars_plot('test 2', [
[0, 5, (2, 3)], [0, 5, (2, 3)],
[1, 8, (4, 5)] [1, 8, (4, 5)]
])) ])))
tk3 = tikzpicture() tk3 = tikzpicture()
tk3.addaxis(errorbars_plot('test 2', [ tk3.add(axis(errorbars_plot('test 2', [
[0, 5, 3], [0, 5, 3],
[4, 8, 4] [4, 8, 4]
])) ])))
s1 = subfloat(tk, 'Caption 1') tk4 = tikzpicture()
s2 = subfloat(tk2, 'Caption 2') tk4.add(axis(coordinates_plot('test', [])))
s3 = subfloat(tk3, 'Caption 3')
s4 = subfloat('', 'Caption 4')
tab = tabular([ s1 = subfloat('Caption 1', childrens=tk1)
[resizebox(s1, 0.5), resizebox(s2, 0.5)], s2 = subfloat('Caption 2', childrens=tk2)
[resizebox(s3, 0.5), resizebox(s4, 0.5)] s3 = subfloat('Caption 3', childrens=tk3)
], columns='cc') s4 = subfloat('Caption 4', childrens=tk4)
print(figure(makebox(tab), '!h')) tab = tabular(rows=[
[s1, s2],
[s3, s4]
], columns='cc', autowrap=resizebox)
f = figure(makebox(tab), '!h')
print(f) # f.childrens[0].childrens[0].childrens)
wrap = """\\documentclass{{article}}
\\usepackage{{amsmath,amsfonts,amssymb}}
\\usepackage{{tabularx}}
\\usepackage{{multirow}}
\\usepackage{{stmaryrd}}
\\usepackage{{hhline}}
\\usepackage{{caption}}
\\usepackage{{tikz}}
\\usetikzlibrary{{arrows}}
\\usetikzlibrary{{positioning}}
\\usepackage{{subfig}}
\\usepackage{{pgfplots}}
\\begin{{document}}
{content}
\\end{{document}}""".format(content=f)
with open('/tmp/test.tex', 'w') as fd:
fd.write(wrap)
os.system('pdflatex /tmp/test.tex')
os.system('evince test.pdf')