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
from .documentclass import *
from .figure import figure
from .formatter import formatter
from .input_element import input_element
from .makebox import makebox
from .resizebox import resizebox
from .subfloat import subfloat
from .table import table, tabledf
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 -*-
from .formatter import formatter
from .element import element
class figure:
class figure(element):
""" Class representing a latex figure. """
fmt = formatter({
templates = {
'caption': '\\caption{{{content}}}',
'content': """\\begin{{figure}}[{options}]
{centered}
{inner}
{caption}{label}
\\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.
Parameters:
- options Options for the figure.
- centered Set to true to add a centering command.
"""
self.inner = inner
super().__init__(parent, childrens, label=label)
self.options = options
self.caption = caption
self.centered = ''
if centered:
self.centered = '\\centering'
def __str__(self):
return self.fmt.format('content', {
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,
'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 -*-
from .formatter import formatter
from .element import element
class makebox:
class makebox(element):
""" Class representing an unindented makebox. """
fmt = formatter({
templates = {
'content': """{noindent}\\makebox[{width}]{{
{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.
Parameters:
@ -21,7 +21,7 @@ class makebox:
\\textwidth.
- noindent Add a \\noindent command before the box.
"""
self.inner = inner
super().__init__(parent, content)
self.width = width
if self.width is None:
@ -33,9 +33,9 @@ class makebox:
if self.noindent:
self.noindent = '\\noindent'
def __str__(self):
return self.fmt.format('content', {
'inner': self.inner,
def content(self):
return self.fmt().format('content', {
'inner': super().content(),
'width': self.width,
'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):
def __init__(self, legend, data, options=[]):
def __init__(self, legend, data, label=None, options=[]):
options = options + [
'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):
rows = []

View File

@ -1,17 +1,18 @@
# -*- encoding: utf-8 -*-
from ..formatter import formatter
from ..element import element
class generic_plot:
class generic_plot(element):
""" Class representing a pgfplots plot. """
fmt = formatter({
templates = {
'options': '+[{content}]',
'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.data = data
self.options = options
@ -24,12 +25,12 @@ class generic_plot:
if type(opt) is not str:
opt = '{}={}'.format(*opt)
opts.append(str(opt))
return self.fmt.format('options', {
return self.fmt().format('options', {
'content': ', '.join(opts)
})
def __str__(self):
return self.fmt.format('content', {
def content(self):
return self.fmt().format('content', {
'data': self.format_data(self.data),
'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 -*-
from .formatter import formatter
from .element import element
class resizebox:
class resizebox(element):
""" Class representing a latex resizebox. """
fmt = formatter({
templates = {
'content': """\\resizebox{{{width}}}{{{height}}}{{
{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.
Parameters:
@ -24,15 +25,15 @@ class resizebox:
- str Use as it is (e.g. "0.5\\linewidth", "!").
- others Use as a percentage of linewidth (e.g. 0.5).
"""
self.inner = inner
super().__init__(parent, content)
self.width = width
if type(width) != 'str':
if type(width) is not str:
self.width = '{}\\linewidth'.format(width)
self.height = height
def __str__(self):
return self.fmt.format('content', {
def content(self):
return self.fmt().format('content', {
'width': self.width,
'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 -*-
from .formatter import formatter
from .element import element
class subfloat:
class subfloat(element):
""" Class representing a latex subfloat. """
fmt = formatter({
templates = {
'content': """\\subfloat[{caption}]{{
{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.inner = inner
def __str__(self):
return self.fmt.format('content', {
def content(self):
return self.fmt().format('content', {
'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 -*-
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. """
fmt = formatter({
templates = {
'content': """\\begin{{tabular}}{{{columns}}}
{inner}
\\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.
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.
- columns String representing columns options.
"""
if type(rows) == str:
columns = rows
rows = []
super().__init__(parent, label=label)
self.columns = columns
self.rows = []
self.autowrap = autowrap
for row in rows:
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.
Parameters:
- 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':
row = ' & '.join(map(str, row))
self.rows.append(row)
if not isinstance(row, row_element):
row = row_element(row, parent=self)
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):
""" 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', {
def content(self):
return self.fmt().format('content', {
'columns': self.columns,
'inner': inner
'inner': super().content()
})

View File

@ -2,5 +2,4 @@
# flake8: noqa
from .plots import *
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
from ..formatter import formatter
from .axis import axis
from ..element import element
class tikzpicture:
class tikzpicture(element):
""" Class representing a latex tikzfigure. """
fmt = formatter({
templates = {
'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))
}
def __init__(self, childrens=None, parent=None, label=None):
super().__init__(parent, childrens, label=label)
def content(self):
return self.fmt().format('content', {
'inner': super().content()
})

View File

@ -1,13 +1,16 @@
# -*- encoding: utf-8 -*-
import os
import sys
sys.path += ['..']
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__":
data = [[1.00000000e+00,
4.57213083e+00],
[2.00000000e+00,
@ -21,30 +24,57 @@ if __name__ == "__main__":
[ 2.00000000e+01,
1.50422028e+02]]
tk = tikzpicture()
tk.addaxis(coordinates_plot('test', data),
xlabel='', ylabel='Computation Time [s]')
tk1 = tikzpicture()
tk1.add(axis(coordinates_plot('test', data),
xlabel='', ylabel='Computation Time [s]'))
tk2 = tikzpicture()
tk2.addaxis(errorbars_plot('test 2', [
tk2.add(axis(errorbars_plot('test 2', [
[0, 5, (2, 3)],
[1, 8, (4, 5)]
]))
])))
tk3 = tikzpicture()
tk3.addaxis(errorbars_plot('test 2', [
tk3.add(axis(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')
tk4 = tikzpicture()
tk4.add(axis(coordinates_plot('test', [])))
tab = tabular([
[resizebox(s1, 0.5), resizebox(s2, 0.5)],
[resizebox(s3, 0.5), resizebox(s4, 0.5)]
], columns='cc')
s1 = subfloat('Caption 1', childrens=tk1)
s2 = subfloat('Caption 2', childrens=tk2)
s3 = subfloat('Caption 3', childrens=tk3)
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')