Initial project version
This commit is contained in:
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
doc/_build
|
||||
.idea
|
||||
__pycache__
|
||||
build/
|
||||
dist/
|
||||
*.egg-info
|
||||
logarithmic.*.so
|
||||
logarithmic.c
|
||||
coordinates.*.so
|
||||
coordinates.c
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Niels A. Müller
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
36
README.md
Normal file
36
README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# TUDPlot
|
||||
|
||||
A package for matplotlib plotting in the corporate style of the TU Darmstadt.
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure dependencies are installed:
|
||||
|
||||
* Python 3.5
|
||||
* matplotlib
|
||||
* numpy
|
||||
|
||||
Then in the source directory run:
|
||||
|
||||
python3 setup.py install
|
||||
|
||||
## Usage
|
||||
|
||||
Use the function `activate` to activate TU styled plotting:
|
||||
|
||||
import tudplot
|
||||
tudplot.activate()
|
||||
|
||||
A call with no arguments will activate a nominal color range of 6 distinct TU colors.
|
||||
One can specify the keyword `full` to enable a range of continuous colors from the full TU color spectrum.
|
||||
|
||||
tudplot.activate(full=10)
|
||||
|
||||
TUDPlot also comes with a basic exporter `saveagr`, to save the current matplotlib figure in xmgrace agr format.
|
||||
|
||||
from matplotlib import pyplot as plt
|
||||
fig = plt.figure()
|
||||
fig.plot([1,2,3], label='Test')
|
||||
# Do more plotting here...
|
||||
# When the figure is complete:
|
||||
tudplot.saveagr('test.agr')
|
49
pygrace/README.md
Normal file
49
pygrace/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Pygrace: A tool to convert matplotlib figure to xmgrace plots
|
||||
|
||||
After plotting the matplotlib figure, call
|
||||
pygrace.saveagr('myfig.agr')
|
||||
to save it as a xmgrace plot.
|
||||
|
||||
## Current state of supperted matplotlib porperties
|
||||
|
||||
The conversion is done by the appropriate classes (`AgrFigure`, `AgrAxis`, ...) defined in the pygrace module.
|
||||
They each only support a small subset of matplotlib properties, see below.
|
||||
|
||||
### Figures
|
||||
|
||||
- canvas size (page size), controllable with `AgrFigure.dpi`
|
||||
- font size scaling, controllable with `AgrFigure.fontscale`
|
||||
- map colors
|
||||
- x/y offset: adds an offset (as fraction of the according edge) to the xmgrace figure. Helpfull if x labels get pushed out of the page.
|
||||
|
||||
### Axes
|
||||
|
||||
- size, axis limits
|
||||
- title
|
||||
- linear or log scale
|
||||
- labels and ticklabels:
|
||||
- turn on/off
|
||||
- position (left, right, bottom, top)
|
||||
- fontsize
|
||||
- legend:
|
||||
- turn on/off
|
||||
- location
|
||||
- fontsize
|
||||
|
||||
### Lines
|
||||
|
||||
- label (for legend)
|
||||
- linestyle
|
||||
- line color
|
||||
- markers:
|
||||
- shape
|
||||
- color
|
||||
- edgewidth
|
||||
- fill (appart from full and none, this is kind of random)
|
||||
|
||||
### Texts:
|
||||
|
||||
- location
|
||||
- fontsize
|
||||
- color
|
||||
|
1
pygrace/__init__.py
Normal file
1
pygrace/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .pygrace import saveagr
|
114
pygrace/_load_stuff.py
Normal file
114
pygrace/_load_stuff.py
Normal file
@ -0,0 +1,114 @@
|
||||
from __future__ import print_function
|
||||
import os.path
|
||||
import os
|
||||
import imp
|
||||
import sys
|
||||
import configparser as ConfigParser
|
||||
|
||||
if os.name == 'nt':
|
||||
path_to_log = os.path.expanduser('~\.auswerten')
|
||||
else:
|
||||
path_to_log = os.path.expanduser('~/.auswerten')
|
||||
path_to_module = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def reload_(filename):
|
||||
(path, name) = os.path.split(filename)
|
||||
(name, ext) = os.path.splitext(name)
|
||||
|
||||
try:
|
||||
file, filename, data = imp.find_module(name, [path])
|
||||
except ImportError:
|
||||
print('No module {} found'.format(name))
|
||||
try:
|
||||
mod = imp.load_module(name, file, filename, data)
|
||||
return mod
|
||||
except UnboundLocalError:
|
||||
pass
|
||||
if file:
|
||||
file.close()
|
||||
|
||||
|
||||
def import_(filename):
|
||||
"""
|
||||
Tries to add random file to sys.modules. Insecure as fuck and predestined to break something
|
||||
(it will overload other modules)
|
||||
"""
|
||||
(path, name) = os.path.split(filename)
|
||||
(name, ext) = os.path.splitext(name)
|
||||
try:
|
||||
return sys.modules[name]
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
file, filename, data = imp.find_module(name, [path])
|
||||
except ImportError:
|
||||
print('No module {} found'.format(name))
|
||||
try:
|
||||
mod = imp.load_module(name, file, filename, data)
|
||||
return mod
|
||||
except UnboundLocalError:
|
||||
pass
|
||||
finally:
|
||||
# Since we may exit via an exception, close fp explicitly.
|
||||
try:
|
||||
if file:
|
||||
file.close()
|
||||
except UnboundLocalError:
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
from shutil import copyfile
|
||||
if os.name == 'nt':
|
||||
copyfile(os.path.join(path_to_module, 'models\myfitmodels.py'), filename)
|
||||
else:
|
||||
copyfile(os.path.join(path_to_module, './models/myfitmodels.py'), filename)
|
||||
# open(filename, 'a').close()
|
||||
|
||||
user_model_path = os.path.join(path_to_log, 'myfitmodels.py')
|
||||
userfitmodels = import_(user_model_path)
|
||||
|
||||
|
||||
def read_grace_conf(mode):
|
||||
__path_to_config = os.path.join(path_to_log, 'grace.conf')
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
config.read(__path_to_config)
|
||||
|
||||
if config.has_section(mode):
|
||||
width = config.getfloat(mode, 'width')
|
||||
height = config.getfloat(mode, 'height')
|
||||
bottom_m = config.getfloat(mode, 'bottom margin')
|
||||
left_m = config.getfloat(mode, 'left margin')
|
||||
top_m = config.getfloat(mode, 'top margin')
|
||||
right_m = config.getfloat(mode, 'right margin')
|
||||
title = config.getint(mode, 'title')
|
||||
haxis_t = config.getint(mode, 'hor axis tick')
|
||||
vaxis_t = config.getint(mode, 'ver axis tick')
|
||||
haxis_l = config.getint(mode, 'hor axis label')
|
||||
vaxis_l = config.getint(mode, 'ver axis label')
|
||||
leg = config.getint(mode, 'legend')
|
||||
else:
|
||||
width = round(11 * 2.54, 2)
|
||||
height = round(8.5 * 2.54, 2)
|
||||
bottom_m = round(0.15 * 8.5 * 2.54, 2)
|
||||
left_m = round(0.15 * 8.5 * 2.54, 2)
|
||||
top_m = round(0.15 * 8.5 * 2.54, 2)
|
||||
right_m = round((11 / 8.5 - 1.15) * 8.5 * 2.54, 2)
|
||||
haxis_l = 100
|
||||
haxis_t = 100
|
||||
vaxis_l = 100
|
||||
vaxis_t = 100
|
||||
leg = 100
|
||||
title = 100
|
||||
if config.has_section('sizes'):
|
||||
sym = config.getfloat('sizes', 'symbol')
|
||||
line = config.getfloat('sizes', 'line')
|
||||
f_line = config.getfloat('sizes', 'fit_line')
|
||||
else:
|
||||
sym = 10
|
||||
line = 1
|
||||
f_line = 1
|
||||
|
||||
return (width, height), (bottom_m, top_m, left_m, right_m), \
|
||||
((haxis_t, haxis_l), (vaxis_t, vaxis_l), title, leg), \
|
||||
(sym, line, f_line)
|
||||
|
158
pygrace/colors.py
Normal file
158
pygrace/colors.py
Normal file
@ -0,0 +1,158 @@
|
||||
import matplotlib as mpl
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from collections import OrderedDict
|
||||
from itertools import cycle
|
||||
|
||||
|
||||
def make_cmap(colors, mapping=True):
|
||||
if isinstance(colors, dict):
|
||||
colors = np.array(colors.values())
|
||||
bit_rgb = np.linspace(0, 1, 256)
|
||||
position = np.linspace(0, 1, len(colors))
|
||||
_cols = colors[:] + 0
|
||||
for i in range(len(colors)):
|
||||
for j in xrange(3):
|
||||
_cols[i][j] = bit_rgb[colors[i][j]]
|
||||
if mapping:
|
||||
cdict = {'red': [], 'green':[], 'blue':[]}
|
||||
for pos, color in zip(position, _cols):
|
||||
cdict['red'].append((pos, color[0], color[0]))
|
||||
cdict['green'].append((pos, color[1], color[1]))
|
||||
cdict['blue'].append((pos, color[2], color[2]))
|
||||
|
||||
cmap = mpl.colors.LinearSegmentedColormap('my_colormap', cdict, 256)
|
||||
else:
|
||||
cmap = mpl.colors.ListedColormap(_cols, 'my_colormap')
|
||||
return cmap
|
||||
|
||||
cm_bw = OrderedDict([('white', (255, 255, 255, 1)),
|
||||
('black', (0, 0, 0, 1))])
|
||||
|
||||
cm_grace = OrderedDict([
|
||||
('red', (255, 0, 0, 255)),
|
||||
('green', (0, 255, 0, 255)),
|
||||
('blue', (0, 0, 255, 255)),
|
||||
('yellow', (255, 255, 0, 255)),
|
||||
('brown', (188, 143, 143, 255)),
|
||||
('grey', (220, 220, 220, 255)),
|
||||
('violet', (148, 0, 211, 255)),
|
||||
('cyan', (0, 255, 255, 255)),
|
||||
('magenta', (255, 0, 255, 255)),
|
||||
('orange', (255, 165, 0, 255)),
|
||||
('indigo', (114, 33, 188, 255)),
|
||||
('maroon', (103, 7, 72, 255)),
|
||||
('turquoise', (64, 224, 208, 255)),
|
||||
('green4', (0, 139, 0, 255))
|
||||
])
|
||||
|
||||
|
||||
cm_tuda = OrderedDict([
|
||||
('tud1a', (93, 133, 195, 255)),
|
||||
('tud2a', (0, 156, 218, 255)),
|
||||
('tud3a', (80, 182, 149, 255)),
|
||||
('tud4a', (175, 204, 80, 255)),
|
||||
('tud5a', (221, 223, 72, 255)),
|
||||
('tud6a', (255, 224, 92, 255)),
|
||||
('tud7a', (248, 186, 60, 255)),
|
||||
('tud8a', (238, 122, 52, 255)),
|
||||
('tud9a', (233, 80, 62, 255)),
|
||||
('tud10a', (201, 48, 142, 255)),
|
||||
('tud11a', (128, 69, 151, 255))
|
||||
])
|
||||
|
||||
cm_tudb = OrderedDict([
|
||||
('tud1b', (0, 90, 169, 255)),
|
||||
('tud2b', (0, 131, 204, 255)),
|
||||
('tud3b', (0, 157, 129, 255)),
|
||||
('tud4b', (153, 192, 0, 255)),
|
||||
('tud5b', (201, 212, 0, 255)),
|
||||
('tud6b', (253, 202, 0, 255)),
|
||||
('tud7b', (245, 163, 0, 255)),
|
||||
('tud8b', (236, 101, 0, 255)),
|
||||
('tud9b', (230, 0, 26, 255)),
|
||||
('tud10b', (166, 0, 132, 255)),
|
||||
('tud11b', (114, 16, 133, 255))
|
||||
])
|
||||
|
||||
cm_tudc = OrderedDict([
|
||||
('tud1c', (0, 78, 138, 255)),
|
||||
('tud2c', (0, 104, 157, 255)),
|
||||
('tud3c', (0, 136, 119, 255)),
|
||||
('tud4c', (127, 171, 22, 255)),
|
||||
('tud5c', (177, 189, 0, 255)),
|
||||
('tud6c', (215, 172, 0, 255)),
|
||||
('tud7c', (210, 135, 0, 255)),
|
||||
('tud8c', (204, 76, 3, 255)),
|
||||
('tud9c', (185, 15, 34, 255)),
|
||||
('tud10c', (149, 17, 105, 255)),
|
||||
('tud11c', (97, 28, 115, 255))
|
||||
])
|
||||
|
||||
|
||||
cm_tudd = OrderedDict([
|
||||
('tud1d', (36, 53, 114, 255)),
|
||||
('tud2d', (0, 78, 115, 255)),
|
||||
('tud3d', (0, 113, 94, 255)),
|
||||
('tud4d', (106, 139, 55, 255)),
|
||||
('tud5d', (153, 166, 4, 255)),
|
||||
('tud6d', (174, 142, 0, 255)),
|
||||
('tud7d', (190, 111, 0, 255)),
|
||||
('tud8d', (169, 73, 19, 255)),
|
||||
('tud9d', (156, 28, 38, 255)),
|
||||
('tud10d', (115, 32, 84, 255)),
|
||||
('tud11d', (76, 34, 106, 255))
|
||||
])
|
||||
|
||||
|
||||
def combine_maps(*args):
|
||||
color_map = OrderedDict()
|
||||
for d in args:
|
||||
for k, v in d.items():
|
||||
color_map[k] = v
|
||||
return color_map
|
||||
|
||||
cm_tud = combine_maps(cm_tuda, cm_tudb, cm_tudc, cm_tudd)
|
||||
|
||||
all_colors = combine_maps(cm_bw, cm_grace, cm_tud)
|
||||
|
||||
colors = cm_tud.values()
|
||||
|
||||
symbols = cycle(['o', 't', 'd', 's', '+'])
|
||||
|
||||
sym_map = {None: '0', 'o': '1', 's': '2', 'd': '3', 't': '6', '+': '9'}
|
||||
|
||||
|
||||
class ColorDict(OrderedDict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ColorDict, self).__init__(*args, **kwargs)
|
||||
for d in [cm_bw, cm_tuda, cm_tudb, cm_tudc, cm_tudd, cm_grace]:
|
||||
old_length = len(self)
|
||||
for k, c in enumerate(d.keys()):
|
||||
rgb = d[c][:3]
|
||||
self[rgb] = (k + old_length, c)
|
||||
self._new = OrderedDict()
|
||||
|
||||
def __contains__(self, item):
|
||||
if isinstance(item, tuple):
|
||||
return item in self.keys()
|
||||
elif isinstance(item. basestring):
|
||||
return any([item == v[1] for v in self.values()])
|
||||
else:
|
||||
return False
|
||||
|
||||
def convert_color(self, color):
|
||||
c = tuple(color[0:3])
|
||||
if c not in self.keys():
|
||||
self[c] = (str(self.__len__()), ''.join('{:02X}'.format(a) for a in c))
|
||||
self._new[c] = (str(self.__len__()-1), ''.join('{:02X}'.format(a) for a in c))
|
||||
return self[c][0]
|
||||
|
||||
def has_index(self, idx):
|
||||
return any([idx == v[0] for v in self.values()])
|
||||
|
||||
def new_colors(self):
|
||||
return self._new
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
355
pygrace/pygrace.py
Normal file
355
pygrace/pygrace.py
Normal file
@ -0,0 +1,355 @@
|
||||
import numpy as np
|
||||
from matplotlib.colors import ColorConverter
|
||||
from matplotlib.backends import backend_agg
|
||||
import matplotlib.pyplot as plt
|
||||
import textwrap
|
||||
from itertools import zip_longest
|
||||
from collections import defaultdict
|
||||
|
||||
from . import tex2grace
|
||||
from .tex2grace import latex_to_xmgrace
|
||||
|
||||
|
||||
def update_labels(labels, axis=None):
|
||||
if axis is None:
|
||||
axis = plt.gca()
|
||||
for line, lb in zip_longest(axis.lines, labels, fillvalue=''):
|
||||
line.set_label(lb)
|
||||
|
||||
|
||||
def sanitize_strings(dic):
|
||||
"""Do some sanitization for strings."""
|
||||
for k in dic:
|
||||
if isinstance(dic[k], str):
|
||||
dic[k].replace('{', '{{')
|
||||
dic[k].replace('}', '}}')
|
||||
|
||||
|
||||
def get_world_coords(artist):
|
||||
"""Get the world coordinates of an artist."""
|
||||
trans = artist.axes.transData.inverted()
|
||||
return trans.transform(artist.get_window_extent())
|
||||
|
||||
|
||||
class AgrText:
|
||||
fmt = """string on
|
||||
string loctype view
|
||||
string {position}
|
||||
string char size {size:.2f}
|
||||
string color {color}
|
||||
string def "{value}"
|
||||
"""
|
||||
|
||||
def get_position(self):
|
||||
trans = self.agr_axis.agr_figure.figure.transFigure.inverted()
|
||||
pos = trans.transform(self.text.get_window_extent())[0]
|
||||
pos = (pos + self.agr_figure.offset) * self.agr_figure.pagescale
|
||||
self.position = '{:.5f}, {:.5f}'.format(*pos)
|
||||
|
||||
def __init__(self, text, agr_axis):
|
||||
self.text = text
|
||||
self.agr_axis = agr_axis
|
||||
self.agr_figure = agr_axis.agr_figure
|
||||
|
||||
self.value = latex_to_xmgrace(text.get_text())
|
||||
self.size = text.get_fontsize() * self.agr_figure.fontscale
|
||||
self.color = AgrLine.color_index(text.get_color())
|
||||
self.get_position()
|
||||
sanitize_strings(self.__dict__)
|
||||
|
||||
def __str__(self):
|
||||
return self.fmt.format(**self.__dict__)
|
||||
|
||||
|
||||
class AgrLine:
|
||||
fmt = """hidden {hidden}
|
||||
type {type}
|
||||
legend "{label}"
|
||||
line linestyle {linestyle}
|
||||
line linewidth {linewidth}
|
||||
line color {color}
|
||||
symbol {marker}
|
||||
symbol color {markeredgecolor}
|
||||
symbol fill color {markerfacecolor}
|
||||
symbol fill pattern {markerfill}
|
||||
symbol linewidth {linewidth}
|
||||
"""
|
||||
width_scale = 2
|
||||
cc = ColorConverter()
|
||||
linestyles = {'None': 0, '-': 1, ':': 2, '--': 3, '-.': 5}
|
||||
markers = defaultdict(
|
||||
lambda: 1,
|
||||
{'': 0, 'None': 0, 'o': 1, 's': 2, 'd': 3, '^': 4, '<': 5, 'v': 6, '>': 7, '+': 8, 'x': 9, '*': 10}
|
||||
)
|
||||
fillstyles = ('none', 'full', 'right', 'left', 'bottom', 'top')
|
||||
colors = ['white', 'black']
|
||||
|
||||
def color_index(col):
|
||||
if col not in AgrLine.colors:
|
||||
AgrLine.colors.append(col)
|
||||
return AgrLine.colors.index(col)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
o = '@type xy\n'
|
||||
for x, y in self.line.get_xydata():
|
||||
o += '{} {}\n'.format(x, y)
|
||||
o += '&'
|
||||
return o
|
||||
|
||||
def get_label(self):
|
||||
lbl = self.line.get_label()
|
||||
self.label = latex_to_xmgrace(lbl) if not lbl.startswith('_line') else ''
|
||||
|
||||
def get_linestyle(self):
|
||||
self.linestyle = self.linestyles[self.line.get_linestyle()]
|
||||
|
||||
def get_linewidth(self):
|
||||
self.linewidth = self.line.get_linewidth() * self.width_scale
|
||||
|
||||
def get_color(self):
|
||||
self.color = AgrLine.color_index(self.line.get_color())
|
||||
|
||||
def get_marker(self):
|
||||
mk = self.line.get_marker()
|
||||
self.marker = self.markers[mk] if mk in self.markers else 1
|
||||
mfc = self.line.get_markerfacecolor()
|
||||
self.markerfacecolor = AgrLine.color_index(mfc)
|
||||
mec = self.line.get_markeredgecolor()
|
||||
self.markeredgecolor = AgrLine.color_index(mec)
|
||||
self.markeredgewidth = self.line.get_markeredgewidth() * self.width_scale
|
||||
self.markerfill = self.fillstyles.index(self.line.get_fillstyle())
|
||||
|
||||
def __init__(self, line, agr_axis):
|
||||
self.agr_axis = agr_axis
|
||||
self.line = line
|
||||
self.hidden = 'false'
|
||||
self.type = 'xy'
|
||||
|
||||
# run all get_ methods
|
||||
for d in dir(self):
|
||||
if d.startswith('get_'):
|
||||
getattr(self, d)()
|
||||
|
||||
sanitize_strings(self.__dict__)
|
||||
|
||||
def __str__(self):
|
||||
return self.fmt.format(**self.__dict__)
|
||||
|
||||
|
||||
class AgrAxis:
|
||||
fmt = """world {world}
|
||||
view {view}
|
||||
title {title}
|
||||
yaxes scale {yscale}
|
||||
yaxis tick major {yticks}
|
||||
xaxis label "{xlabel}"
|
||||
xaxis label place {xlabelpos}
|
||||
xaxis label char size {labelsize}
|
||||
xaxis tick major {xticks}
|
||||
xaxis ticklabel {xticklabel}
|
||||
xaxis ticklabel place {xticklabelpos}
|
||||
xaxis ticklabel char size {ticklabelsize}
|
||||
xaxes scale {xscale}
|
||||
yaxis label "{ylabel}"
|
||||
yaxis label place {ylabelpos}
|
||||
yaxis label char size {labelsize}
|
||||
yaxis ticklabel {yticklabel}
|
||||
yaxis ticklabel place {yticklabelpos}
|
||||
yaxis ticklabel char size {ticklabelsize}
|
||||
legend {legend}
|
||||
legend loctype world
|
||||
legend {legend_pos}
|
||||
legend char size {legend_fontsize}
|
||||
"""
|
||||
|
||||
def get_world(self):
|
||||
xmin, xmax = self.axis.get_xlim()
|
||||
ymin, ymax = self.axis.get_ylim()
|
||||
self.world = '{:.3}, {:.3}, {:.3}, {:.3}'.format(xmin, ymin, xmax, ymax)
|
||||
box = self.axis.get_position()
|
||||
fx, fy = self.axis.figure.get_size_inches()
|
||||
sx, sy = self.agr_figure.pagescale
|
||||
offx, offy = self.agr_figure.offset
|
||||
self.view = '{:.3}, {:.3}, {:.3}, {:.3}'.format(
|
||||
(box.xmin + offx)*sx, (box.ymin + offy)*sy,
|
||||
(box.xmax + offx)*sx, (box.ymax + offy)*sy
|
||||
)
|
||||
|
||||
def get_title(self):
|
||||
self.title = latex_to_xmgrace(self.axis.get_title())
|
||||
|
||||
def get_xyaxis(self):
|
||||
self.xlabel = latex_to_xmgrace(self.axis.get_xlabel())
|
||||
xpos = self.axis.xaxis.get_label_position()
|
||||
self.xlabelpos = 'normal' if xpos == 'bottom' else 'opposite'
|
||||
|
||||
self.ylabel = latex_to_xmgrace(self.axis.get_ylabel())
|
||||
ypos = self.axis.yaxis.get_label_position()
|
||||
self.ylabelpos = 'normal' if ypos == 'left' else 'opposite'
|
||||
|
||||
xsc = self.axis.get_xscale()
|
||||
self.xscale = 'Logarithmic' if 'log' in xsc else 'Normal'
|
||||
xticks = self.axis.get_xticks()
|
||||
if xsc == 'log':
|
||||
self.xticks = (xticks[1:] / xticks[:-1]).mean()
|
||||
else:
|
||||
self.xticks = (xticks[1:] - xticks[:-1]).mean()
|
||||
|
||||
self.xticklabel = 'on' if any([x.get_visible() for x in self.axis.get_xticklabels()]) else 'off'
|
||||
xpos = self.axis.xaxis.get_ticks_position()
|
||||
if xpos == 'both':
|
||||
self.xticklabelpos = 'both'
|
||||
elif xpos == 'top':
|
||||
self.xticklabelpos = 'opposite'
|
||||
else:
|
||||
self.xticklabelpos = 'normal'
|
||||
|
||||
xtlpos = set([x.get_position()[1] for x in self.axis.get_xticklabels() if x.get_visible()])
|
||||
if len(xtlpos) == 0:
|
||||
self.xticklabel = 'off'
|
||||
self.xticklabelpos = 'normal'
|
||||
elif len(xtlpos) == 1:
|
||||
self.xticklabel = 'on'
|
||||
self.xticklabelpos = 'opposite' if 1 in xtlpos else 'normal'
|
||||
else:
|
||||
self.xticklabel = 'on'
|
||||
self.xticklabelpos = 'both'
|
||||
|
||||
ytlpos = set([x.get_position()[0] for x in self.axis.get_yticklabels() if x.get_visible()])
|
||||
if len(ytlpos) == 0:
|
||||
self.yticklabel = 'off'
|
||||
self.yticklabelpos = 'normal'
|
||||
elif len(ytlpos) == 1:
|
||||
self.yticklabel = 'on'
|
||||
self.yticklabelpos = 'opposite' if 1 in ytlpos else 'normal'
|
||||
else:
|
||||
self.yticklabel = 'on'
|
||||
self.yticklabelpos = 'both'
|
||||
|
||||
ysc = self.axis.get_yscale()
|
||||
self.yscale = 'Logarithmic' if 'log' in ysc else 'Normal'
|
||||
yticks = self.axis.get_yticks()
|
||||
if ysc == 'log':
|
||||
self.yticks = (yticks[1:] / yticks[:-1]).mean()
|
||||
else:
|
||||
self.yticks = (yticks[1:] - yticks[:-1]).mean()
|
||||
|
||||
self.labelsize = self.axis.xaxis.get_label().get_fontsize() * self.agr_figure.fontscale
|
||||
fs = self.axis.xaxis.get_ticklabels()[0].get_fontsize()
|
||||
self.ticklabelsize = fs * self.agr_figure.fontscale
|
||||
|
||||
def get_legend(self):
|
||||
leg = self.axis.get_legend()
|
||||
if leg is None:
|
||||
self.legend = 'off'
|
||||
self.legend_pos = '0, 0'
|
||||
self.legend_fontsize = 0
|
||||
else:
|
||||
self.legend = 'on'
|
||||
for lbl, line in zip(leg.get_texts(), leg.get_lines()):
|
||||
pass
|
||||
pos = get_world_coords(leg)
|
||||
self.legend_pos = '{:.3f}, {:.3f}'.format(*pos.diagonal())
|
||||
self.legend_fontsize = leg.get_texts()[0].get_fontsize() * self.agr_figure.fontscale
|
||||
|
||||
def __init__(self, axis, agr_fig):
|
||||
self.agr_figure = agr_fig
|
||||
self.axis = axis
|
||||
|
||||
# run all get_ methods
|
||||
for d in dir(self):
|
||||
if d.startswith('get_'):
|
||||
getattr(self, d)()
|
||||
|
||||
sanitize_strings(self.__dict__)
|
||||
self.lines = {'s{}'.format(i): AgrLine(l, self) for i, l in enumerate(axis.lines)}
|
||||
self.texts = [AgrText(t, self) for t in self.axis.texts]
|
||||
|
||||
def __str__(self):
|
||||
o = self.fmt.format(**self.__dict__)
|
||||
for k, l in self.lines.items():
|
||||
o += textwrap.indent(str(l), prefix=k + ' ')
|
||||
for txt in self.texts:
|
||||
o += 'with string\n'
|
||||
o += textwrap.indent(str(txt), prefix=' ')
|
||||
return o
|
||||
|
||||
|
||||
class AgrFigure:
|
||||
dpi = 96
|
||||
fontscale = 0.5
|
||||
fmt = """@version 50125
|
||||
@page size {page}
|
||||
"""
|
||||
data_fmt = "@target {target}\n{data}\n"""
|
||||
|
||||
def collect_data(self):
|
||||
d = {}
|
||||
for ia, ax in self.axes.items():
|
||||
for il, ln in ax.lines.items():
|
||||
d['{}.{}'.format(ia.upper(), il.upper())] = ln.data
|
||||
return d
|
||||
|
||||
def get_figprops(self):
|
||||
fx, fy = self.figure.get_size_inches()
|
||||
scx, scy = (1 + self.offset)
|
||||
# scy = (1 + self.offset_vertical)
|
||||
self.page = '{}, {}'.format(int(scx * self.dpi * fx), int(scy * self.dpi * fy))
|
||||
self.fontscale = AgrFigure.fontscale / min(fx * scx, fy * scy)
|
||||
|
||||
self.pagescale = np.array([fx, fy]) / min(fx * scx, fy * scy)
|
||||
|
||||
def __init__(self, figure, offset_horizontal=0, offset_vertical=0, convert_latex=True):
|
||||
tex2grace.do_latex_conversion = convert_latex
|
||||
self.figure = figure
|
||||
self.offset = np.array([offset_horizontal, offset_vertical])
|
||||
# make sure to draw the figure...
|
||||
canv = backend_agg.FigureCanvasAgg(figure)
|
||||
canv.draw()
|
||||
|
||||
# run all get_ methods
|
||||
for d in dir(self):
|
||||
if d.startswith('get_'):
|
||||
getattr(self, d)()
|
||||
sanitize_strings(self.__dict__)
|
||||
|
||||
self.axes = {'g{}'.format(i): AgrAxis(ax, self) for i, ax in enumerate(self.figure.axes)}
|
||||
|
||||
def __str__(self):
|
||||
o = self.fmt.format(**self.__dict__)
|
||||
|
||||
for i, col in enumerate(AgrLine.colors):
|
||||
# in matplotlib-1.5 to_rgb can not handle 'none', this was fixed in 2.0
|
||||
rgb = [int(x * 255) for x in AgrLine.cc.to_rgba(col)[:3]]
|
||||
o += '@map color {i} to ({rgb[0]}, {rgb[1]}, {rgb[2]}), "{col}"\n'.format(i=i, rgb=rgb, col=col)
|
||||
|
||||
for k, ax in self.axes.items():
|
||||
o += textwrap.indent("on\nhidden false\ntype XY\nstacked false\n", prefix='@{} '.format(k))
|
||||
o += '@with {}\n'.format(k)
|
||||
o += textwrap.indent(str(ax), prefix='@ ')
|
||||
|
||||
for k, d in self.collect_data().items():
|
||||
o += self.data_fmt.format(target=k, data=d)
|
||||
|
||||
return o
|
||||
|
||||
|
||||
def saveagr(fname, figure=None, offset_x=0, offset_y=0, convert_latex=True):
|
||||
"""
|
||||
Save figure as xmgrace plot.
|
||||
|
||||
If no figure is provided, this will save the current figure.
|
||||
|
||||
Args:
|
||||
fname: Filename of the agr plot
|
||||
figure (opt.): Matplotlib figure to save, if None gcf() is used.
|
||||
offset_x, offset_y (opt.): Add an offest in x or y direction to the xmgrace plot.
|
||||
convert_latex (opt.): If latex strings will be converted to xmgrace.
|
||||
"""
|
||||
if figure is None:
|
||||
figure = plt.gcf()
|
||||
with open(fname, 'w') as f:
|
||||
af = AgrFigure(figure, offset_horizontal=offset_x, offset_vertical=offset_y,
|
||||
convert_latex=convert_latex)
|
||||
f.write(str(af))
|
201
pygrace/str2format.py
Normal file
201
pygrace/str2format.py
Normal file
@ -0,0 +1,201 @@
|
||||
from __future__ import print_function
|
||||
import re
|
||||
|
||||
small_greek = {'alpha': u'\u03b1',
|
||||
'beta': u'\u03b2',
|
||||
'gamma': u'\u03b3',
|
||||
'delta': u'\u03b4',
|
||||
'epsilon': u'\u03b5',
|
||||
'zeta': u'\u03b6',
|
||||
'eta': u'\u03b7',
|
||||
'theta': u'\u03b8',
|
||||
'iota': u'\u03b9',
|
||||
'kappa': u'\u03ba',
|
||||
'lambda': u'\u03bb',
|
||||
'mu': u'\u03bc',
|
||||
'nu': u'\u03bd',
|
||||
'xi': u'\u03be',
|
||||
'omicron': u'\u03bf',
|
||||
'pi': u'\u03c0',
|
||||
'rho': u'\u03c1',
|
||||
'varsigma': u'\u03c2',
|
||||
'sigma': u'\u03c3',
|
||||
'tau': u'\u03c4',
|
||||
'ypsilon': u'\u03c5',
|
||||
'phi': u'\u03c6',
|
||||
'chi': u'\u03c7',
|
||||
'psi': u'\u03c8',
|
||||
'omega': u'\u03c9'
|
||||
}
|
||||
|
||||
big_greek = {'Alpha': u'\u0391',
|
||||
'Beta': u'\u0392',
|
||||
'Gamma': u'\u0393',
|
||||
'Delta': u'\u0394',
|
||||
'Epsilon': u'\u0395',
|
||||
'Zeta': u'\u0396',
|
||||
'Eta': u'\u0397',
|
||||
'Theta': u'\u0398',
|
||||
'Iota': u'\u0399',
|
||||
'Kappa': u'\u039a',
|
||||
'Lambda': u'\u039b',
|
||||
'Mu': u'\u039c',
|
||||
'Nu': u'\u039d',
|
||||
'Xi': u'\u039e',
|
||||
'Omicron': u'\u039f',
|
||||
'Pi': u'\u03a0',
|
||||
'Rho': u'\u03a1',
|
||||
'Varsigma': u'\u03a2',
|
||||
'Sigma': u'\u03a3',
|
||||
'Tau': u'\u03a4',
|
||||
'Ypsilon': u'\u03a5',
|
||||
'Phi': u'\u03a6',
|
||||
'Chi': u'\u03a7',
|
||||
'Psi': u'\u03a8',
|
||||
'Omega': u'\u03a9'
|
||||
}
|
||||
|
||||
special_chars = {'infty': u'\u221e',
|
||||
'int': u'\u222b',
|
||||
'perp': u'\u27c2',
|
||||
'para': u'\u2225',
|
||||
'leftarrow': u'\u21d0',
|
||||
'rightarrow': u'\u21d2',
|
||||
'leftrightarrow': u'\u21d4',
|
||||
'\n': '<br>',
|
||||
'*': u'\u00b7',
|
||||
'plmin': u'\u00b1'}
|
||||
|
||||
small_greek_grace = {'alpha': r'\f{Symbol}a\f{}',
|
||||
'beta': r'\f{Symbol}b\f{}',
|
||||
'gamma': r'\f{Symbol}g\f{}',
|
||||
'delta': r'\f{Symbol}d\f{}',
|
||||
'epsilon': r'\f{Symbol}e\f{}',
|
||||
'zeta': r'\f{Symbol}z\f{}',
|
||||
'eta': r'\f{Symbol}h\f{}',
|
||||
'theta': r'\f{Symbol}q\f{}',
|
||||
'iota': r'\f{Symbol}i\f{}',
|
||||
'kappa': r'\f{Symbol}k\f{}',
|
||||
'lambda': r'\f{Symbol}l\f{}',
|
||||
'mu': r'\f{Symbol}m\f{}',
|
||||
'nu': r'\f{Symbol}n\f{}',
|
||||
'xi': r'\f{Symbol}x\f{}',
|
||||
'omicron': r'\f{Symbol}o\f{}',
|
||||
'pi': r'\f{Symbol}p\f{}',
|
||||
'rho': r'\f{Symbol}r\f{}',
|
||||
'sigma': r'\f{Symbol}s\f{}',
|
||||
'tau': r'\f{Symbol}t\f{}',
|
||||
'ypsilon': r'\f{Symbol}u\f{}',
|
||||
'phi': r'\f{Symbol}f\f{}',
|
||||
'chi': r'\f{Symbol}c\f{}',
|
||||
'psi': r'\f{Symbol}y\f{}',
|
||||
'omega': r'\f{Symbol}w\f{}'
|
||||
}
|
||||
|
||||
big_greek_grace = {'Alpha': r'\f{Symbol}A\f{}',
|
||||
'Beta': r'\f{Symbol}B\f{}',
|
||||
'Gamma': r'\f{Symbol}g\f{}',
|
||||
'Delta': r'\f{Symbol}D\f{}',
|
||||
'Epsilon': r'\f{Symbol}E\f{}',
|
||||
'Zeta': r'\f{Symbol}Z\f{}',
|
||||
'Eta': r'\f{Symbol}H\f{}',
|
||||
'Theta': r'\f{Symbol}Q\f{}',
|
||||
'Iota': r'\f{Symbol}I\f{}',
|
||||
'Kappa': r'\f{Symbol}K\f{}',
|
||||
'Lambda': r'\f{Symbol}L\f{}',
|
||||
'Mu': r'\f{Symbol}M\f{}',
|
||||
'Nu': r'\f{Symbol}N\f{}',
|
||||
'Xi': r'\f{Symbol}X\f{}',
|
||||
'Omicron': r'\f{Symbol}O\f{}',
|
||||
'Pi': r'\f{Symbol}P\f{}',
|
||||
'Rho': r'\f{Symbol}R\f{}',
|
||||
'Sigma': r'\f{Symbol}S\f{}',
|
||||
'Tau': r'\f{Symbol}T\f{}',
|
||||
'Ypsilon': r'\f{Symbol}U\f{}',
|
||||
'Phi': r'\f{Symbol}F\f{}',
|
||||
'Chi': r'\f{Symbol}C\f{}',
|
||||
'Psi': r'\f{Symbol}U\f{}',
|
||||
'Omega': r'\f{Symbol}Q\f{}'
|
||||
}
|
||||
|
||||
special_chars_grace = {'infty': r'\f{Symbol}\c%\f{}'}
|
||||
|
||||
|
||||
full_chars = dict()
|
||||
for d in [big_greek, small_greek, special_chars]:
|
||||
full_chars.update(d)
|
||||
|
||||
full_char_reverse = {y: x for x, y in full_chars.items()}
|
||||
|
||||
|
||||
def _replace_unicode(text):
|
||||
for k, v in full_chars.items():
|
||||
text = text.replace(k, v)
|
||||
return text
|
||||
|
||||
|
||||
def _replacesub(text):
|
||||
sub_pattern_1 = re.compile('(_\{\S*?\})')
|
||||
sub_pattern_2 = re.compile('_\S')
|
||||
for s in sub_pattern_1.findall(text):
|
||||
text = text.replace(s, u'<sub>{0:}</sub>'.format(s[2:-1]))
|
||||
for s in sub_pattern_2.findall(text):
|
||||
text = text.replace(s, u'<sub>{0:}</sub>'.format(s[1:]))
|
||||
return text
|
||||
|
||||
|
||||
def _replacesup(text):
|
||||
sup_pattern_1 = re.compile('\^\{\S*?\}')
|
||||
sup_pattern_2 = re.compile('\^\S')
|
||||
for s in sup_pattern_1.findall(text):
|
||||
text = text.replace(s, u'<sup>{0:}</sup>'.format(s.strip()[2:-1]))
|
||||
for s in sup_pattern_2.findall(text):
|
||||
text = text.replace(s, u'<sup>{0:}</sup>'.format(s.strip()[1:]))
|
||||
return text
|
||||
|
||||
|
||||
def str2format(text):
|
||||
text = _replacesub(text)
|
||||
text = _replacesup(text)
|
||||
text = _replace_unicode(text)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def format2str(text):
|
||||
for k, v in full_char_reverse.items():
|
||||
text = text.replace(k, v)
|
||||
for k, v in [('<sub>', '_{'), ('</sub>', '}'), ('<sup>', '^{'), ('</sup>', '}')]:
|
||||
text = text.replace(k, v)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def format2grace(text):
|
||||
text = format2str(text)
|
||||
for k, v in [('_{', r'\s'), ('}', r'\N'), ('^{', r'\S'), ('}', r'\N')]:
|
||||
text = text.replace(k, v)
|
||||
for k, v in big_greek_grace.items():
|
||||
text = text.replace(k, v)
|
||||
for k, v in small_greek_grace.items():
|
||||
text = text.replace(k, v)
|
||||
for k, v in special_chars_grace.items():
|
||||
text = text.replace(k, v)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def grace2str(text):
|
||||
text = text.replace(r'\s', '_{').replace('\S', '^{')
|
||||
text = text.replace(r'\N', '}')
|
||||
text = text.replace(r'\f{}', '')
|
||||
|
||||
return text
|
||||
|
||||
if __name__ == '__main__':
|
||||
stringtext = 'M_{infty}[1-alpha exp(-x/T_{1,1})^{beta_1}]'
|
||||
print('Input:\t{}'.format(stringtext))
|
||||
print(u'Output:\t{}'.format(str2format(stringtext)))
|
||||
print('Input 2:\t{}'.format(format2str(str2format(stringtext))))
|
||||
print('Grace:\t{}'.format(format2grace(str2format(stringtext))))
|
||||
print(u'Output 2:\t{}'.format(str2format(format2str(str2format(stringtext)))))
|
37
pygrace/tex2grace.py
Normal file
37
pygrace/tex2grace.py
Normal file
@ -0,0 +1,37 @@
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
import re
|
||||
|
||||
patterns = OrderedDict()
|
||||
patterns['\$'] = ''
|
||||
patterns[r'\\math(?:tt|sf|it)({.+})'] = r'\1'
|
||||
patterns[r'\^({.+}|.)'] = r'\S\1\N'
|
||||
patterns[r'\_({.+}|.)'] = r'\s\1\N'
|
||||
# Remove any left over latex groups as the last step
|
||||
patterns[r'[{}]'] = ''
|
||||
|
||||
# Greek letters in xmgrace are written by switching to symbol-font:
|
||||
# "\x a\f{}" will print an alpha and switch back to normal font
|
||||
greek = {
|
||||
'alpha': 'a', 'beta': 'b', 'gamma': 'g', 'delta': 'd', 'epsilon': 'e', 'zeta': 'z',
|
||||
'eta': 'h', 'theta': 'q', 'iota': 'i', 'kappa': 'k', 'lambda': 'l', 'mu': 'm',
|
||||
'nu': 'n', 'xi': 'x', 'omicron': 'o', 'pi': 'p', 'rho': 'r', 'sigma': 's',
|
||||
'tau': 't', 'upsilon': 'u', 'phi': 'f', 'chi': 'c', 'psi': 'y', 'omega': 'w',
|
||||
'varphi': 'j', 'varepsilon': 'e', 'vartheta': 'J', 'varrho': 'r'
|
||||
}
|
||||
for latex, xmg in greek.items():
|
||||
patt = r'\\{}'.format(latex)
|
||||
repl = r'\\x {}\\f{{}}'.format(xmg)
|
||||
patterns[patt] = repl
|
||||
|
||||
|
||||
do_latex_conversion = True
|
||||
|
||||
|
||||
def latex_to_xmgrace(string):
|
||||
logging.debug('Convert to xmgrace: {}'.format(string))
|
||||
if do_latex_conversion and '$' in string:
|
||||
for patt, repl in patterns.items():
|
||||
string = re.sub(patt, repl, string)
|
||||
logging.debug('To -> {}'.format(string))
|
||||
return string
|
13
setup.py
Normal file
13
setup.py
Normal file
@ -0,0 +1,13 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='tudplot',
|
||||
description='Matplotlib styling in TU Darmstadt corporate design',
|
||||
author_email='niels.mueller@physik.tu-darmstadt.de',
|
||||
|
||||
packages=['tudplot', 'pygrace'],
|
||||
version='0.1',
|
||||
requires=['matplotlib'],
|
||||
package_data={'tudplot': ['tud.mplstyle']},
|
||||
zip_safe=True,
|
||||
)
|
77
tudplot/__init__.py
Executable file
77
tudplot/__init__.py
Executable file
@ -0,0 +1,77 @@
|
||||
import os
|
||||
|
||||
import numpy
|
||||
import matplotlib as mpl
|
||||
from matplotlib import pyplot
|
||||
from cycler import cycler
|
||||
|
||||
from .xmgrace import export_to_agr, load_agr_data
|
||||
from .tud import tudcolors, nominal_colors, sequential_colors
|
||||
from .utils import facet_plot, CurvedText as curved_text
|
||||
|
||||
|
||||
def activate(scheme='b', full=False, sequential=False, cmap='blue-red', **kwargs):
|
||||
"""
|
||||
Activate the tud design.
|
||||
|
||||
Args:
|
||||
scheme (opt.): Color scheme to activate, default is 'b'.
|
||||
full (opt.):
|
||||
Activate the full color palette. If False a smaller color palette is used.
|
||||
If a number N is given, N colors will be chosen based on a interpolation of
|
||||
all tudcolors.
|
||||
sequential (opt.): Activate a number of sequential colors from a color map.
|
||||
cmap (opt.):
|
||||
Colormap to use for sequential colors, can be either from `~tudplot.tud.cmaps`
|
||||
or any matplotlib color map. Range of the color map values can be given as
|
||||
cmap_min and cmap_max, respectively.
|
||||
**kwargs: Any matplotlib rc paramter may be given as keyword argument.
|
||||
"""
|
||||
mpl.pyplot.style.use(os.path.join(os.path.dirname(__file__), 'tud.mplstyle'))
|
||||
|
||||
if full:
|
||||
if isinstance(full, int):
|
||||
cmap = mpl.colors.LinearSegmentedColormap.from_list('tud{}'.format(scheme),
|
||||
tudcolors[scheme])
|
||||
colors = [cmap(x) for x in numpy.linspace(0, 1, full)]
|
||||
else:
|
||||
colors = tudcolors[scheme]
|
||||
elif sequential:
|
||||
colors = sequential_colors(sequential, cmap=cmap, min=kwargs.pop('cmap_min', 0),
|
||||
max=kwargs.pop('cmap_max', 1))
|
||||
else:
|
||||
colors = nominal_colors[scheme]
|
||||
|
||||
mpl.rcParams['axes.prop_cycle'] = cycler('color', colors)
|
||||
|
||||
|
||||
def saveagr(filename, figure=None, convert_latex=True):
|
||||
"""
|
||||
Save the current figure in xmgrace format.
|
||||
|
||||
Args:
|
||||
filename: Agrfile to save the figure to
|
||||
figure (opt.):
|
||||
Figure that will be saved, if not given the current figure is saved
|
||||
"""
|
||||
figure = figure or pyplot.gcf()
|
||||
figure.canvas.draw()
|
||||
export_to_agr(figure, filename, convert_latex=convert_latex)
|
||||
|
||||
|
||||
def markfigure(x, y, s, ax=None, **kwargs):
|
||||
if ax is None:
|
||||
ax = pyplot.gca()
|
||||
kwargs['transform'] = ax.transAxes
|
||||
kwargs['ha'] = 'center'
|
||||
kwargs['va'] = 'center'
|
||||
# kwargs.setdefault('fontsize', 'large')
|
||||
ax.text(x, y, s, **kwargs)
|
||||
|
||||
|
||||
def marka(x, y):
|
||||
markfigure(x, y, '(a)')
|
||||
|
||||
|
||||
def markb(x, y):
|
||||
markfigure(x, y, '(b)')
|
157
tudplot/altair.py
Executable file
157
tudplot/altair.py
Executable file
@ -0,0 +1,157 @@
|
||||
|
||||
import altair
|
||||
from altair import Config, Color, Shape, Column, Row, Encoding, Scale, Axis
|
||||
from random import randint
|
||||
import os
|
||||
import logging
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from .tud import nominal_colors, full_colors
|
||||
|
||||
|
||||
|
||||
def filter_nulltime_json(data):
|
||||
if 'time' in data:
|
||||
data = data[data.time > 0]
|
||||
return altair.pipe(data, altair.to_json)
|
||||
|
||||
altair.renderers.enable('notebook')
|
||||
altair.data_transformers.register('json_logtime', filter_nulltime_json)
|
||||
altair.data_transformers.enable('json_logtime')
|
||||
|
||||
def my_theme(*args, **kwargs):
|
||||
return {
|
||||
'range': {
|
||||
'ordinal': altair.VgScheme('viridis'),
|
||||
'ramp': {'scheme': 'viridis'}
|
||||
}
|
||||
}
|
||||
|
||||
altair.themes.register('my-theme', my_theme)
|
||||
altair.themes.enable('my-theme')
|
||||
|
||||
|
||||
class BaseMixin(Encoding):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('scale', altair.Scale(zero=False))
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class LogMixin(BaseMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['scale'] = altair.Scale(type='log')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class X(altair.X, BaseMixin):
|
||||
pass
|
||||
|
||||
|
||||
class Y(altair.Y, BaseMixin):
|
||||
pass
|
||||
|
||||
|
||||
class LogX(altair.X, LogMixin):
|
||||
pass
|
||||
|
||||
|
||||
class LogY(altair.Y, LogMixin):
|
||||
pass
|
||||
|
||||
|
||||
class DataHandler(altair.Data):
|
||||
|
||||
def __init__(self, df):
|
||||
self._filename = '.altair.json'
|
||||
with open(self._filename, 'w') as f:
|
||||
f.write(df.to_json())
|
||||
super().__init__(url=self._filename)
|
||||
|
||||
|
||||
class Chart(altair.Chart):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def encode(self, *args, color=None, **kwargs):
|
||||
if isinstance(color, str):
|
||||
if color.endswith(':F'):
|
||||
field = color[:-2]
|
||||
color = color.replace(':F', ':N')
|
||||
self.configure_scale(nominalColorRange=full_colors(len(set(self._data[field]))))
|
||||
|
||||
for arg in args:
|
||||
if isinstance(arg, altair.X):
|
||||
kwargs['x'] = arg
|
||||
elif isinstance(arg, altair.Y):
|
||||
kwargs['y'] = arg
|
||||
return super().encode(color=color, **kwargs)
|
||||
|
||||
def to_mpl(self):
|
||||
d = self.to_dict()
|
||||
fmt = 'o' if d.get('mark', 'point') is 'point' else '-'
|
||||
|
||||
def encode(data, encoding, **kwargs):
|
||||
logging.debug(str(kwargs))
|
||||
if 'column' in encoding:
|
||||
channel = encoding.pop('column')
|
||||
ncols = len(data[channel.get('field')].unique())
|
||||
for col, (column, df) in enumerate(data.groupby(channel.get('field'))):
|
||||
ax = plt.gca() if col > 0 else None
|
||||
plt.subplot(kwargs.get('nrows', 1), ncols, col + 1, sharey=ax).set_title(column)
|
||||
encode(df, encoding.copy(), secondary_column=col > 0, **kwargs.copy())
|
||||
elif 'color' in encoding:
|
||||
channel = encoding.pop('color')
|
||||
if channel.get('type') == 'quantitative':
|
||||
colors = full_colors(len(data[channel.get('field')].unique()))
|
||||
else:
|
||||
colors = nominal_colors['b']
|
||||
while len(colors) < len(data[channel.get('field')].unique()):
|
||||
colors *= 2
|
||||
for color, (column, df) in zip(colors, data.groupby(channel.get('field'))):
|
||||
if 'label' in kwargs:
|
||||
label = kwargs.pop('label') + ', {}'.format(column)
|
||||
else:
|
||||
label = str(column)
|
||||
encode(df, encoding.copy(), color=color, label=label, **kwargs.copy())
|
||||
elif 'shape' in encoding:
|
||||
channel = encoding.pop('shape')
|
||||
markers = ['h', 'v', 'o', 's', '^', 'D', '<', '>']
|
||||
while len(markers) < len(data[channel.get('field')].unique()):
|
||||
markers *= 2
|
||||
logging.debug(str(data[channel.get('field')].unique()))
|
||||
for marker, (column, df) in zip(markers, data.groupby(channel.get('field'))):
|
||||
if 'label' in kwargs:
|
||||
label = kwargs.pop('label') + ', {}'.format(column)
|
||||
else:
|
||||
label = str(column)
|
||||
encode(df, encoding.copy(), marker=marker, label=label, **kwargs.copy())
|
||||
|
||||
else:
|
||||
x_field = encoding.get('x').get('field')
|
||||
y_field = encoding.get('y').get('field')
|
||||
plt.xlabel(x_field)
|
||||
if not kwargs.pop('secondary_column', False):
|
||||
plt.ylabel(y_field)
|
||||
else:
|
||||
plt.tick_params(axis='y', which='both', labelleft='off', labelright='off')
|
||||
if 'scale' in encoding.get('x'):
|
||||
plt.xscale(encoding['x']['scale'].get('type', 'linear'))
|
||||
if 'scale' in encoding.get('y'):
|
||||
plt.yscale(encoding['y']['scale'].get('type', 'linear'))
|
||||
plt.plot(data[x_field], data[y_field], fmt, **kwargs)
|
||||
plt.legend(loc='best', fontsize='small')
|
||||
|
||||
encode(self._data, d.get('encoding'))
|
||||
plt.tight_layout(pad=0.5)
|
||||
|
||||
|
||||
class Arrhenius(Chart):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# self.transform_data(calculate=[Formula(field='1000K/T', expr='1000/datum.T')])
|
||||
self.data['1000 K / T'] = 1000 / self.data['T']
|
||||
self.encode(x=X('1000 K / T'))
|
53
tudplot/tex2grace.py
Executable file
53
tudplot/tex2grace.py
Executable file
@ -0,0 +1,53 @@
|
||||
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
import re
|
||||
|
||||
patterns = OrderedDict()
|
||||
patterns['\$'] = ''
|
||||
patterns['¹'] = r'\\S1\\N'
|
||||
patterns['²'] = r'\\S2\\N'
|
||||
patterns['³'] = r'\\S3\\N'
|
||||
patterns['⁴'] = r'\\S4\\N'
|
||||
patterns['⁵'] = r'\\S5\\N'
|
||||
patterns['⁶'] = r'\\S6\\N'
|
||||
patterns['⁷'] = r'\\S7\\N'
|
||||
patterns['⁸'] = r'\\S8\\N'
|
||||
patterns['⁹'] = r'\\S9\\N'
|
||||
patterns['⁻'] = r'\\S-\\N'
|
||||
patterns[r'\\star'] = '*'
|
||||
patterns[r'\\,'] = r'\\-\\- \\+\\+'
|
||||
patterns[r'\\[;:.]'] = ''
|
||||
patterns[r'\\math(?:tt|sf|it|rm)({[^}]+})'] = r'\1'
|
||||
patterns[r'\^({[^}]+}|.)'] = r'\\S\1\\N'
|
||||
patterns[r'\_({[^}]+}|.)'] = r'\\s\1\\N'
|
||||
# Remove any left over latex groups as the last step
|
||||
patterns[r'[{}]'] = ''
|
||||
|
||||
# now any patterns that do need braces...
|
||||
patterns[r'\\tilde '] = r'\\v{{0.1}}~\\v{{-0.1}}\\M{{1}}'
|
||||
|
||||
# Greek letters in xmgrace are written by switching to symbol-font:
|
||||
# "\x a\f{}" will print an alpha and switch back to normal font
|
||||
greek = {
|
||||
'alpha': 'a', 'beta': 'b', 'gamma': 'g', 'delta': 'd', 'epsilon': 'e', 'zeta': 'z',
|
||||
'eta': 'h', 'theta': 'q', 'iota': 'i', 'kappa': 'k', 'lambda': 'l', 'mu': 'm',
|
||||
'nu': 'n', 'xi': 'x', 'omicron': 'o', 'pi': 'p', 'rho': 'r', 'sigma': 's',
|
||||
'tau': 't', 'upsilon': 'u', 'phi': 'f', 'chi': 'c', 'psi': 'y', 'omega': 'w',
|
||||
'varphi': 'j', 'varepsilon': 'e', 'vartheta': 'J', 'varrho': 'r',
|
||||
'Phi': 'F',
|
||||
'langle': r'\#{{e1}}', 'rangle': r'\#{{f1}}', 'infty': r'\\c%\\C', 'cdot': r'\#{{d7}}',
|
||||
'sqrt': r'\#{{d6}}', 'propto': r'\#{{b5}}', 'approx': r'\#{{bb}}'
|
||||
}
|
||||
for latex, xmg in greek.items():
|
||||
patt = r'\\{}'.format(latex)
|
||||
repl = r'\\x {}\\f{{{{}}}}'.format(xmg)
|
||||
patterns[patt] = repl
|
||||
|
||||
|
||||
def latex_to_xmgrace(string):
|
||||
logging.debug('Convert to xmgrace: {}'.format(string))
|
||||
for patt, repl in patterns.items():
|
||||
string = re.sub(patt, repl, string)
|
||||
logging.debug('To -> {}'.format(string))
|
||||
return string
|
7
tudplot/tud.mplstyle
Executable file
7
tudplot/tud.mplstyle
Executable file
@ -0,0 +1,7 @@
|
||||
font.size: 16
|
||||
lines.linewidth: 1.5
|
||||
lines.markeredgewidth: 1.5
|
||||
lines.markersize: 6
|
||||
markers.fillstyle: full
|
||||
figure.figsize: 8, 6
|
||||
savefig.dpi: 300
|
42
tudplot/tud.py
Normal file
42
tudplot/tud.py
Normal file
@ -0,0 +1,42 @@
|
||||
import re
|
||||
import matplotlib as mpl
|
||||
import numpy
|
||||
|
||||
tudcolors = {
|
||||
'a': ('#5D85C3', '#009CDA', '#50B695', '#AFCC50', '#DDDF48', '#FFE05C',
|
||||
'#F8BA3C', '#EE7A34', '#E9503E', '#C9308E', '#804597'),
|
||||
'b': ('#005AA9', '#0083CC', '#009D81', '#99C000', '#C9D400', '#FDCA00',
|
||||
'#F5A300', '#EC6500', '#E6001A', '#A60084', '#721085'),
|
||||
'c': ('#004E8A', '#00689D', '#008877', '#7FAB16', '#B1BD00', '#D7AC00',
|
||||
'#D28700', '#CC4C03', '#B90F22', '#951169', '#611C73'),
|
||||
'd': ('#243572', '#004E73', '#00715E', '#6A8B22', '#99A604', '#AE8E00',
|
||||
'#BE6F00', '#A94913', '#961C26', '#732054', '#4C226A'),
|
||||
}
|
||||
|
||||
# Store each color value in the dict as defined in the Style-Guide (e.g. tud9c)
|
||||
tudcolors.update({'tud{}{}'.format(i + 1, s): col for s in tudcolors for i, col in enumerate(tudcolors[s])})
|
||||
|
||||
color_maps = {
|
||||
'blue-red': (tudcolors['tud1b'], tudcolors['tud9b']),
|
||||
'black-green': ('black', tudcolors['tud5c']),
|
||||
'violett-green': (tudcolors['tud11c'], tudcolors['tud5b'])
|
||||
}
|
||||
|
||||
|
||||
nominal_colors = {scheme: [tudcolors[scheme][i] for i in [1, 8, 3, 9, 6, 2]] for scheme in 'abcd'}
|
||||
|
||||
|
||||
def full_colors(N, scheme='b'):
|
||||
cmap = mpl.colors.LinearSegmentedColormap.from_list('tud{}'.format(scheme), tudcolors[scheme])
|
||||
return ['#{:02x}{:02x}{:02x}'.format(*cmap(x, bytes=True)[:3]) for x in numpy.linspace(0, 1, N)]
|
||||
|
||||
|
||||
def sequential_colors(N, cmap='blue-red', min=0, max=1):
|
||||
if cmap in color_maps:
|
||||
cmap = mpl.colors.LinearSegmentedColormap.from_list('tud_{}'.format(cmap), color_maps[cmap])
|
||||
elif '-' in cmap:
|
||||
cols = [tudcolors[k] if 'tud' in k else k for k in cmap.split('-')]
|
||||
cmap = mpl.colors.LinearSegmentedColormap.from_list(cmap, cols)
|
||||
else:
|
||||
cmap = mpl.pyplot.get_cmap(cmap)
|
||||
return ['#{:02x}{:02x}{:02x}'.format(*cmap(x, bytes=True)[:3]) for x in numpy.linspace(min, max, N)]
|
220
tudplot/utils.py
Normal file
220
tudplot/utils.py
Normal file
@ -0,0 +1,220 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib as mpl
|
||||
from collections import Iterable
|
||||
from matplotlib.cbook import flatten
|
||||
from itertools import cycle
|
||||
|
||||
|
||||
def facet_plot(dframe, facets, props, ydata, layout=None, newfig=True, figsize=None,
|
||||
legend=True, individual_legends=False, hide_additional_axes=True, zorder='default', **kwargs):
|
||||
if newfig:
|
||||
nr_facets = len(dframe.groupby(facets))
|
||||
if layout is None:
|
||||
for i in range(2, nr_facets // 2):
|
||||
if nr_facets % i == 0:
|
||||
layout = (nr_facets // i, i)
|
||||
break
|
||||
if layout is None:
|
||||
n = int(np.ceil(nr_facets / 2))
|
||||
layout = (n, 2)
|
||||
fig, axs = plt.subplots(
|
||||
nrows=layout[0],
|
||||
ncols=layout[1],
|
||||
sharex=True, sharey=True, figsize=figsize
|
||||
)
|
||||
if hide_additional_axes:
|
||||
for ax in fig.axes[nr_facets:]:
|
||||
ax.set_axis_off()
|
||||
else:
|
||||
fig = plt.gcf()
|
||||
axs = fig.axes
|
||||
|
||||
cycl = cycle(plt.rcParams['axes.prop_cycle'])
|
||||
prop_styles = {ps: next(cycl) for ps, _ in dframe.groupby(props)}
|
||||
|
||||
if zorder is 'default':
|
||||
dz = 1
|
||||
zorder = 0
|
||||
elif zorder is 'reverse':
|
||||
dz = -1
|
||||
zorder = 0
|
||||
else:
|
||||
dz = 0
|
||||
|
||||
if legend:
|
||||
ax0 = fig.add_subplot(111, frame_on=False, zorder=-9999)
|
||||
ax0.set_axis_off()
|
||||
plot_kwargs = kwargs.copy()
|
||||
for k in ['logx', 'logy', 'loglog']:
|
||||
plot_kwargs.pop(k, None)
|
||||
for l, p in prop_styles.items():
|
||||
ax0.plot([], label=str(l), **p, **plot_kwargs)
|
||||
ax0.legend(loc='center left', bbox_to_anchor=(1, 0.5), fontsize='x-small')
|
||||
for ax, (ps, df) in zip(flatten(axs), dframe.groupby(facets, squeeze=False)):
|
||||
for prop, df_prop in df.groupby(props):
|
||||
df_prop[ydata].plot(ax=ax, label=str(prop), zorder=zorder, **prop_styles[prop], **kwargs)
|
||||
zorder += dz
|
||||
# ax.title(0.5, 0.1, '{},{}'.format(*ps), transform=ax.transAxes, fontsize='small')
|
||||
|
||||
ax.set_title('; '.join([str(x) for x in ps]) if isinstance(ps, tuple) else str(ps), fontsize='x-small')
|
||||
if individual_legends:
|
||||
ax.legend(fontsize='x-small')
|
||||
|
||||
plt.sca(ax)
|
||||
rect = (0, 0, 0.85, 1) if legend else (0, 0, 1, 1)
|
||||
plt.tight_layout(rect=rect, pad=0.1)
|
||||
return fig, axs
|
||||
|
||||
|
||||
class CurvedText(mpl.text.Text):
|
||||
"""A text object that follows an arbitrary curve."""
|
||||
|
||||
def __init__(self, x, y, text, axes, **kwargs):
|
||||
super(CurvedText, self).__init__(x[0],y[0],' ', axes, **kwargs)
|
||||
|
||||
axes.add_artist(self)
|
||||
|
||||
# # saving the curve:
|
||||
self.__x = x
|
||||
self.__y = y
|
||||
self.__zorder = self.get_zorder()
|
||||
|
||||
# # creating the text objects
|
||||
self.__Characters = []
|
||||
for c in text:
|
||||
t = mpl.text.Text(0, 0, c, **kwargs)
|
||||
# resetting unnecessary arguments
|
||||
t.set_ha('center')
|
||||
t.set_rotation(0)
|
||||
t.set_zorder(self.__zorder +1)
|
||||
|
||||
self.__Characters.append((c,t))
|
||||
axes.add_artist(t)
|
||||
|
||||
# # overloading some member functions, to assure correct functionality
|
||||
# # on update
|
||||
def set_zorder(self, zorder):
|
||||
super(CurvedText, self).set_zorder(zorder)
|
||||
self.__zorder = self.get_zorder()
|
||||
for c,t in self.__Characters:
|
||||
t.set_zorder(self.__zorder+1)
|
||||
|
||||
def draw(self, renderer, *args, **kwargs):
|
||||
"""
|
||||
Overload of the Text.draw() function. Do not do
|
||||
do any drawing, but update the positions and rotation
|
||||
angles of self.__Characters.
|
||||
"""
|
||||
self.update_positions(renderer)
|
||||
|
||||
def update_positions(self,renderer):
|
||||
"""
|
||||
Update positions and rotations of the individual text elements.
|
||||
"""
|
||||
|
||||
# preparations
|
||||
|
||||
# # determining the aspect ratio:
|
||||
# # from https://stackoverflow.com/a/42014041/2454357
|
||||
|
||||
# # data limits
|
||||
xlim = self.axes.get_xlim()
|
||||
ylim = self.axes.get_ylim()
|
||||
# # Axis size on figure
|
||||
figW, figH = self.axes.get_figure().get_size_inches()
|
||||
# # Ratio of display units
|
||||
_, _, w, h = self.axes.get_position().bounds
|
||||
# # final aspect ratio
|
||||
aspect = ((figW * w)/(figH * h))*(ylim[1]-ylim[0])/(xlim[1]-xlim[0])
|
||||
|
||||
# points of the curve in figure coordinates:
|
||||
x_fig,y_fig = (
|
||||
np.array(l) for l in zip(*self.axes.transData.transform([
|
||||
(i,j) for i,j in zip(self.__x,self.__y)
|
||||
]))
|
||||
)
|
||||
|
||||
# point distances in figure coordinates
|
||||
x_fig_dist = (x_fig[1:]-x_fig[:-1])
|
||||
y_fig_dist = (y_fig[1:]-y_fig[:-1])
|
||||
r_fig_dist = np.sqrt(x_fig_dist**2+y_fig_dist**2)
|
||||
|
||||
# arc length in figure coordinates
|
||||
l_fig = np.insert(np.cumsum(r_fig_dist),0,0)
|
||||
|
||||
# angles in figure coordinates
|
||||
rads = np.arctan2((y_fig[1:] - y_fig[:-1]),(x_fig[1:] - x_fig[:-1]))
|
||||
degs = np.rad2deg(rads)
|
||||
|
||||
|
||||
rel_pos = 10
|
||||
for c,t in self.__Characters:
|
||||
# finding the width of c:
|
||||
t.set_rotation(0)
|
||||
t.set_va('center')
|
||||
bbox1 = t.get_window_extent(renderer=renderer)
|
||||
w = bbox1.width
|
||||
h = bbox1.height
|
||||
|
||||
# ignore all letters that don't fit:
|
||||
if rel_pos+w/2 > l_fig[-1]:
|
||||
t.set_alpha(0.0)
|
||||
rel_pos += w
|
||||
continue
|
||||
|
||||
elif c != ' ':
|
||||
t.set_alpha(1.0)
|
||||
|
||||
# finding the two data points between which the horizontal
|
||||
# center point of the character will be situated
|
||||
# left and right indices:
|
||||
il = np.where(rel_pos+w/2 >= l_fig)[0][-1]
|
||||
ir = np.where(rel_pos+w/2 <= l_fig)[0][0]
|
||||
|
||||
# if we exactly hit a data point:
|
||||
if ir == il:
|
||||
ir += 1
|
||||
|
||||
# how much of the letter width was needed to find il:
|
||||
used = l_fig[il]-rel_pos
|
||||
rel_pos = l_fig[il]
|
||||
|
||||
# relative distance between il and ir where the center
|
||||
# of the character will be
|
||||
fraction = (w/2-used)/r_fig_dist[il]
|
||||
|
||||
# # setting the character position in data coordinates:
|
||||
# # interpolate between the two points:
|
||||
x = self.__x[il]+fraction*(self.__x[ir]-self.__x[il])
|
||||
y = self.__y[il]+fraction*(self.__y[ir]-self.__y[il])
|
||||
|
||||
# getting the offset when setting correct vertical alignment
|
||||
# in data coordinates
|
||||
t.set_va(self.get_va())
|
||||
bbox2 = t.get_window_extent(renderer=renderer)
|
||||
|
||||
bbox1d = self.axes.transData.inverted().transform(bbox1)
|
||||
bbox2d = self.axes.transData.inverted().transform(bbox2)
|
||||
dr = np.array(bbox2d[0]-bbox1d[0])
|
||||
|
||||
# the rotation/stretch matrix
|
||||
rad = rads[il]
|
||||
rot_mat = np.array([
|
||||
[np.cos(rad), np.sin(rad)*aspect],
|
||||
[-np.sin(rad)/aspect, np.cos(rad)]
|
||||
])
|
||||
|
||||
# # computing the offset vector of the rotated character
|
||||
drp = np.dot(dr,rot_mat)
|
||||
|
||||
# setting final position and rotation:
|
||||
t.set_position(np.array([x,y])+drp)
|
||||
t.set_rotation(degs[il])
|
||||
|
||||
t.set_va('center')
|
||||
t.set_ha('center')
|
||||
|
||||
# updating rel_pos to right edge of character
|
||||
rel_pos += w-used
|
||||
|
461
tudplot/xmgrace.py
Executable file
461
tudplot/xmgrace.py
Executable file
@ -0,0 +1,461 @@
|
||||
|
||||
import re
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from matplotlib.colors import ColorConverter
|
||||
from matplotlib.cbook import is_string_like
|
||||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
|
||||
from .tud import tudcolors
|
||||
from .tex2grace import latex_to_xmgrace
|
||||
|
||||
|
||||
def indexed(list, default=None):
|
||||
def index(arg):
|
||||
for i, v in enumerate(list):
|
||||
if (isinstance(v, tuple) and arg in v) or arg == v:
|
||||
return i
|
||||
return default
|
||||
return index
|
||||
|
||||
|
||||
def escapestr(s):
|
||||
raw_map = {8: r'\b', 7: r'\a', 12: r'\f', 10: r'\n', 13: r'\r', 9: r'\t', 11: r'\v'}
|
||||
return r''.join(i if ord(i) > 32 else raw_map.get(ord(i), i) for i in s)
|
||||
|
||||
|
||||
def get_viewport_coords(artist):
|
||||
"""
|
||||
Get the viewport coordinates of an artist.
|
||||
"""
|
||||
fxy = artist.figure.get_size_inches()
|
||||
fxy /= fxy.min()
|
||||
trans = artist.figure.transFigure.inverted()
|
||||
return trans.transform(artist.get_window_extent()) * fxy[np.newaxis, :]
|
||||
|
||||
|
||||
def get_world_coords(artist):
|
||||
"""
|
||||
Get the world coordinates of an artist.
|
||||
"""
|
||||
trans = artist.axes.transData.inverted()
|
||||
return trans.transform(artist.get_window_extent())
|
||||
|
||||
|
||||
def get_world(axis):
|
||||
xmin, xmax = axis.get_xlim()
|
||||
ymin, ymax = axis.get_ylim()
|
||||
return '{}, {}, {}, {}'.format(xmin, ymin, xmax, ymax)
|
||||
|
||||
|
||||
def get_view(axis):
|
||||
box = axis.get_position()
|
||||
fx, fy = axis.figure.get_size_inches()
|
||||
sx = fx / min(fx, fy)
|
||||
sy = fy / min(fx, fy)
|
||||
c = np.array([box.xmin*sx, box.ymin*sy, box.xmax*sx, box.ymax*sy])
|
||||
return '{:.3}, {:.3}, {:.3}, {:.3}'.format(*c)
|
||||
|
||||
|
||||
def get_major_ticks(dim):
|
||||
def get_major_dticks(axis):
|
||||
ticks = getattr(axis, 'get_{}ticks'.format(dim))()
|
||||
scale = getattr(axis, 'get_{}scale'.format(dim))()
|
||||
if scale is 'log':
|
||||
value = (ticks[1:] / ticks[:-1]).mean()
|
||||
else:
|
||||
value = (ticks[1:] - ticks[:-1]).mean()
|
||||
|
||||
return value
|
||||
|
||||
return get_major_dticks
|
||||
agr_attr_lists = {
|
||||
# Linestyles in xmgrace: None are styles that are by default
|
||||
# not defined in matplotlib (longer dashes and double dots)
|
||||
# First entry should always be None, since index starts at 1
|
||||
'linestyle': ['None', '-', ':', '--', None, '-.', None, None, None],
|
||||
'marker': ['None', 'o', 's', 'd', '^', '<', 'v', '>', '+', 'x', '*']
|
||||
}
|
||||
|
||||
|
||||
def get_ticklabels_on(dim):
|
||||
def get_ticklabels(axis):
|
||||
tl = getattr(axis, f'{dim}axis').get_ticklabels()
|
||||
return 'off' if all([x.get_text() == '' for x in tl]) else 'on'
|
||||
return get_ticklabels
|
||||
|
||||
|
||||
def get_legend(axis):
|
||||
if axis.get_legend() is not None:
|
||||
return 'on'
|
||||
else:
|
||||
return 'off'
|
||||
|
||||
|
||||
def get_legend_position(axis):
|
||||
leg = axis.get_legend()
|
||||
if leg is not None:
|
||||
return '{:.3f}, {:.3f}'.format(*get_viewport_coords(leg).diagonal())
|
||||
else:
|
||||
return '0, 0'
|
||||
|
||||
|
||||
def get_text_position(text):
|
||||
#return '{:.3f}, {:.3f}'.format(*get_viewport_coords(text)[0])
|
||||
return '{:.3f}, {:.3f}'.format(*get_viewport_coords(text).mean(axis=0))
|
||||
|
||||
|
||||
def get_arrow_coordinates(text):
|
||||
arrow = text.arrow_patch
|
||||
trans = text.axes.transData.inverted()
|
||||
xy = trans.transform(arrow.get_path().vertices[[0, 2]])
|
||||
#xy = get_viewport_coords(text)
|
||||
return '{:.3f}, {:.3f}, {:.3f}, {:.3f}'.format(*xy[0], *xy[1])
|
||||
|
||||
|
||||
class StaticAttribute:
|
||||
"""
|
||||
A static attribute that just writes a line to the agr file if it is set.
|
||||
Functions also as a base class for other attribute classes.
|
||||
"""
|
||||
|
||||
def __init__(self, key, fmt):
|
||||
"""
|
||||
Args:
|
||||
key: The name of the attribute.
|
||||
fmt: The string which is written to the agr file.
|
||||
"""
|
||||
self.key = key
|
||||
self.fmt = fmt
|
||||
|
||||
def format(self, source=None, **kwargs):
|
||||
"""
|
||||
Return the (formatted) string of the attribute.
|
||||
|
||||
Args:
|
||||
source: The python object, which is only included here for signature reasons
|
||||
"""
|
||||
return self.fmt
|
||||
|
||||
|
||||
class ValueAttribute(StaticAttribute):
|
||||
"""
|
||||
An attribute which writes a key value pair into the agr file.
|
||||
The agr string has the format: '{fmt} {value}'
|
||||
"""
|
||||
attr_lists = {
|
||||
'linestyle': ('None', '-', ':', '--', None, '-.', None, None, None),
|
||||
'marker': (('', 'None'), 'o', 's', ('d', 'D'), '^', '<', 'v', '>', '+', 'x', '*'),
|
||||
'fillstyle': ('none', 'full', ),
|
||||
'color': ['white', 'black'],
|
||||
}
|
||||
|
||||
def reset_colors():
|
||||
ValueAttribute.attr_lists['color'] = ['white', 'black']
|
||||
|
||||
def _get_value(self, source, convert_latex=True):
|
||||
value = getattr(source, 'get_{}'.format(self.key))()
|
||||
if isinstance(value, str):
|
||||
if convert_latex:
|
||||
value = latex_to_xmgrace(value)
|
||||
else:
|
||||
value = value.replace(r'{}', r'{{}}').replace('{{{}}}', '{{}}')
|
||||
if not self.index:
|
||||
value = '"{}"'.format(value)
|
||||
return value
|
||||
|
||||
def __init__(self, *args, index=None, function=None, condition=None):
|
||||
"""
|
||||
Args:
|
||||
*args: Arguments of super().__init__()
|
||||
index:
|
||||
True if value should be mapped to an index. If this is a str this will
|
||||
be used as the index lists key.
|
||||
function: A function that is used to fetch the value from the source.
|
||||
condition: A function that decides if the attribute is written to the agr file.
|
||||
"""
|
||||
super().__init__(*args)
|
||||
|
||||
if index:
|
||||
if index is True:
|
||||
self.index = self.key
|
||||
else:
|
||||
self.index = index
|
||||
self.attr_lists.setdefault(self.index, [])
|
||||
else:
|
||||
self.index = False
|
||||
|
||||
if function is not None:
|
||||
self._get_value = lambda x, **kwargs: function(x)
|
||||
self.condition = condition or (lambda x: True)
|
||||
|
||||
def format(self, source, convert_latex=True, **kwargs):
|
||||
value = self._get_value(source, convert_latex=convert_latex)
|
||||
if not self.condition(value):
|
||||
return None
|
||||
if self.index:
|
||||
attr_list = self.attr_lists[self.index]
|
||||
index = indexed(attr_list)(str(value))
|
||||
if index is None:
|
||||
try:
|
||||
attr_list.append(value)
|
||||
index = attr_list.index(value)
|
||||
except AttributeError:
|
||||
print('index not found:', value, index, attr_list)
|
||||
index = 1
|
||||
value = index
|
||||
logging.debug('fmt: {}, value: {}'.format(self.fmt, value))
|
||||
return ' '.join([self.fmt, str(value)])
|
||||
|
||||
agr_line_attrs = [
|
||||
StaticAttribute('hidden', 'hidden false'),
|
||||
StaticAttribute('type', 'type xy'),
|
||||
ValueAttribute('label', 'legend', condition=lambda lbl: re.search(r'\\sl\\Nine\d+', lbl) is None),
|
||||
ValueAttribute('linestyle', 'line linestyle', index=True),
|
||||
ValueAttribute('linewidth', 'line linewidth'),
|
||||
ValueAttribute('color', 'line color', index=True),
|
||||
ValueAttribute('marker', 'symbol', index=True),
|
||||
ValueAttribute('fillstyle', 'symbol fill pattern', index=True),
|
||||
ValueAttribute('markeredgecolor', 'symbol color', index='color'),
|
||||
ValueAttribute('markerfacecolor', 'symbol fill color', index='color'),
|
||||
ValueAttribute('markeredgewidth', 'symbol linewidth'),
|
||||
]
|
||||
|
||||
agr_axis_attrs = [
|
||||
StaticAttribute('xaxis', 'frame background pattern 1'),
|
||||
StaticAttribute('xaxis', 'xaxis label char size 1.0'),
|
||||
StaticAttribute('yaxis', 'yaxis label char size 1.0'),
|
||||
StaticAttribute('xaxis', 'xaxis ticklabel char size 1.0'),
|
||||
StaticAttribute('yaxis', 'yaxis ticklabel char size 1.0'),
|
||||
ValueAttribute('world', 'world', function=get_world),
|
||||
ValueAttribute('view', 'view', function=get_view),
|
||||
ValueAttribute('title', 'subtitle'),
|
||||
ValueAttribute('xlabel', 'xaxis label'),
|
||||
ValueAttribute('ylabel', 'yaxis label'),
|
||||
ValueAttribute('xscale', 'xaxes scale Logarithmic', condition=lambda scale: 'log' in scale),
|
||||
ValueAttribute('xscale', 'xaxis ticklabel format power', condition=lambda scale: 'log' in scale),
|
||||
ValueAttribute('xscale', 'xaxis ticklabel prec 0', condition=lambda scale: 'log' in scale),
|
||||
ValueAttribute('xscale', 'xaxis tick minor ticks', function=lambda ax: 9 if 'log' in ax.get_xscale() else 4),
|
||||
|
||||
ValueAttribute('yscale', 'yaxes scale Logarithmic', condition=lambda scale: 'log' in scale),
|
||||
ValueAttribute('yscale', 'yaxis ticklabel format power', condition=lambda scale: 'log' in scale),
|
||||
ValueAttribute('yscale', 'yaxis ticklabel prec 0', condition=lambda scale: 'log' in scale),
|
||||
ValueAttribute('xscale', 'yaxis tick minor ticks', function=lambda ax: 9 if 'log' in ax.get_yscale() else 4),
|
||||
|
||||
ValueAttribute('xticks', 'xaxis tick major', function=get_major_ticks('x')),
|
||||
ValueAttribute('yticks', 'yaxis tick major', function=get_major_ticks('y')),
|
||||
ValueAttribute('xticklabels', 'xaxis ticklabel', function=get_ticklabels_on('x')),
|
||||
ValueAttribute('yticklabels', 'yaxis ticklabel', function=get_ticklabels_on('y')),
|
||||
ValueAttribute('xlabelposition', 'xaxis label place',
|
||||
function=lambda ax: 'opposite' if ax.xaxis.get_label_position() == 'top' else 'normal'),
|
||||
ValueAttribute('xtickposition', 'xaxis ticklabel place',
|
||||
function=lambda ax: 'opposite' if all([t.get_position()[1] >= 0.9 for t in ax.xaxis.get_ticklabels()]) else 'normal'),
|
||||
ValueAttribute('ylabelposition', 'yaxis label place',
|
||||
function=lambda ax: 'opposite' if ax.yaxis.get_label_position() == 'right' else 'normal'),
|
||||
ValueAttribute('ytickposition', 'yaxis ticklabel place',
|
||||
function=lambda ax: 'opposite' if all([t.get_position()[0] >= 0.9 for t in ax.yaxis.get_ticklabels()]) else 'normal'),
|
||||
# tax.yaxis.get_ticks_position() == 'right' else 'normal'),
|
||||
ValueAttribute('legend', 'legend', function=get_legend),
|
||||
StaticAttribute('legend', 'legend loctype view'),
|
||||
StaticAttribute('legend', 'legend char size 1.0'),
|
||||
ValueAttribute('legend', 'legend', function=get_legend_position)
|
||||
]
|
||||
|
||||
agr_text_attrs = [
|
||||
StaticAttribute('string', 'on'),
|
||||
StaticAttribute('string', 'loctype view'),
|
||||
StaticAttribute('string', 'char size 1.0'),
|
||||
ValueAttribute('position', '', function=get_text_position),
|
||||
ValueAttribute('text', 'def')
|
||||
]
|
||||
|
||||
agr_arrow_attrs = [
|
||||
StaticAttribute('line', 'on'),
|
||||
StaticAttribute('line', 'loctype world'),
|
||||
StaticAttribute('line', 'color 1'),
|
||||
StaticAttribute('line', 'linewidth 2'),
|
||||
StaticAttribute('line', 'linestyle 1'),
|
||||
StaticAttribute('line', 'arrow 2'),
|
||||
ValueAttribute('line', '', function=get_arrow_coordinates),
|
||||
]
|
||||
|
||||
class AgrFile:
|
||||
head = '@version 50125\n'
|
||||
body = tail = ''
|
||||
indent = 0
|
||||
kwargs = {}
|
||||
|
||||
def writeline(self, text, part='body', **kwargs):
|
||||
self.kwargs = {**self.kwargs, **kwargs}
|
||||
content = getattr(self, part)
|
||||
|
||||
content += '@' + ' ' * self.indent + escapestr(text.format(**self.kwargs)) + '\n'
|
||||
setattr(self, part, content)
|
||||
|
||||
def writedata(self, data):
|
||||
self.tail += '@target {axis}.{line}\n@type xy\n'.format(**self.kwargs)
|
||||
for x, y in data:
|
||||
if np.isfinite([x, y]).all():
|
||||
self.tail += '{} {}\n'.format(x, y)
|
||||
self.tail += '&\n'
|
||||
|
||||
def save(self, filename):
|
||||
with open(filename, 'w') as file:
|
||||
file.write(self.head)
|
||||
file.write(self.body)
|
||||
file.write(self.tail)
|
||||
|
||||
|
||||
def _process_attributes(attrs, source, agr, prefix=''):
|
||||
for attr, attr_dict in attrs.items():
|
||||
attr_type = attr_dict['type']
|
||||
if 'static' in attr_type:
|
||||
value = ''
|
||||
elif 'function' in attr_type:
|
||||
value = attr_dict['function'](source)
|
||||
else:
|
||||
value = getattr(source, 'get_{}'.format(attr))()
|
||||
if 'condition' in attr_dict:
|
||||
if not attr_dict['condition'](value):
|
||||
continue
|
||||
if is_string_like(value):
|
||||
value = latex_to_xmgrace(value)
|
||||
if 'index' in attr_type:
|
||||
attr_list = agr_attr_lists[attr_dict.get('maplist', attr)]
|
||||
index = indexed(attr_list)(value)
|
||||
if index is None:
|
||||
if 'map' in attr_type:
|
||||
attr_list.append(value)
|
||||
index = attr_list.index(value)
|
||||
else:
|
||||
index = 1
|
||||
value = index
|
||||
|
||||
agr.writeline(prefix + attr_dict['fmt'], attr=attr, value=value)
|
||||
|
||||
|
||||
def process_attributes(attrs, source, agr, prefix='', **kwargs):
|
||||
for attr in attrs:
|
||||
fmt = attr.format(source, **kwargs)
|
||||
if fmt is not None:
|
||||
agr.writeline(prefix + fmt)
|
||||
|
||||
|
||||
def export_to_agr(figure, filename, **kwargs):
|
||||
"""
|
||||
Export a matplotlib figure to xmgrace format.
|
||||
"""
|
||||
ValueAttribute.reset_colors()
|
||||
cc = ColorConverter()
|
||||
agr = AgrFile()
|
||||
# agr_attr_lists['color'] = ['white', 'black']
|
||||
# agr_colors =
|
||||
papersize = figure.get_size_inches()*120
|
||||
agr.writeline('page size {}, {}'.format(*papersize))
|
||||
agr.writeline('default char size {}'.format(mpl.rcParams['font.size'] / 12))
|
||||
for i, axis in enumerate(figure.axes):
|
||||
|
||||
agr_axis = 'g{}'.format(i)
|
||||
agr.indent = 0
|
||||
agr.writeline('{axis} on', axis=agr_axis)
|
||||
agr.writeline('{axis} hidden false')
|
||||
agr.writeline('{axis} type XY')
|
||||
agr.writeline('{axis} stacked false')
|
||||
agr.writeline('with {axis}')
|
||||
agr.indent = 4
|
||||
|
||||
process_attributes(agr_axis_attrs, axis, agr, **kwargs)
|
||||
|
||||
for j, line in enumerate(axis.lines):
|
||||
agr.kwargs['line'] = 's{}'.format(j)
|
||||
process_attributes(agr_line_attrs, line, agr, '{line} ', **kwargs)
|
||||
agr.writedata(line.get_xydata())
|
||||
|
||||
for text in axis.texts:
|
||||
agr.indent = 0
|
||||
agr.writeline('with string')
|
||||
agr.indent = 4
|
||||
process_attributes(agr_text_attrs, text, agr, 'string ', **kwargs)
|
||||
|
||||
# this is a text of an arrow-annotation
|
||||
if hasattr(text, 'arrow_patch'):
|
||||
agr.indent = 0
|
||||
agr.writeline('with line')
|
||||
agr.indent = 4
|
||||
agr.writeline(f'line {agr_axis}')
|
||||
process_attributes(agr_arrow_attrs, text, agr, 'line ', **kwargs)
|
||||
agr.indent = 0
|
||||
agr.writeline('line def')
|
||||
|
||||
|
||||
agr.indent = 0
|
||||
tudcol_rev = {}
|
||||
for name, color in tudcolors.items():
|
||||
if isinstance(color, str):
|
||||
rgba, = cc.to_rgba_array(color)
|
||||
tudcol_rev[tuple(rgba)] = name
|
||||
|
||||
for i, color in enumerate(ValueAttribute.attr_lists['color']):
|
||||
# print(i, color)
|
||||
if color is not 'none':
|
||||
rgba, = cc.to_rgba_array(color)
|
||||
rgb_tuple = tuple(int(255 * c) for c in rgba[:3])
|
||||
color_name = tudcol_rev.get(tuple(rgba), color)
|
||||
agr.writeline('map color {index} to {rgb}, "{color}"',
|
||||
part='head', index=i, rgb=rgb_tuple, color=color_name)
|
||||
|
||||
agr.save(filename)
|
||||
|
||||
|
||||
def load_agr_data(agrfile, nolabels=False):
|
||||
"""
|
||||
Load all named data sets from an agrfile.
|
||||
"""
|
||||
graphs = OrderedDict()
|
||||
cur_graph = None
|
||||
target = None
|
||||
with open(agrfile, 'r', errors='replace') as f:
|
||||
lines = f.readlines()
|
||||
for org_line in lines:
|
||||
line = org_line.lower()
|
||||
if '@with' in line:
|
||||
graph_id = line.split()[1]
|
||||
if graph_id not in graphs:
|
||||
graphs[graph_id] = {}
|
||||
cur_graph = graphs[graph_id]
|
||||
elif 'legend' in line and cur_graph is not None:
|
||||
ma = re.search('([sS]\d+) .+ "(.*)"', org_line)
|
||||
if ma is not None:
|
||||
label = ma.group(2)
|
||||
sid = ma.group(1).lower()
|
||||
if label == '' or nolabels:
|
||||
gid = [k for k, v in graphs.items() if v is cur_graph][0]
|
||||
label = '{}.{}'.format(gid, sid)
|
||||
cur_graph[sid] = {'label': label}
|
||||
elif '@target' in line:
|
||||
ma = re.search('(g\d+)\.(s\d+)', line.lower())
|
||||
gid = ma.group(1)
|
||||
sid = ma.group(2)
|
||||
target = []
|
||||
if sid not in graphs[gid]:
|
||||
graphs[gid][sid] = {'label': '{}.{}'.format(gid, sid)}
|
||||
graphs[gid][sid]['data'] = target
|
||||
elif target is not None and '@type' in line:
|
||||
continue
|
||||
elif '&' in line:
|
||||
target = None
|
||||
elif target is not None:
|
||||
target.append([float(d) for d in line.split()])
|
||||
|
||||
data = OrderedDict()
|
||||
for _, graph in graphs.items():
|
||||
for _, set in graph.items():
|
||||
if 'data' in set:
|
||||
data[set['label']] = np.array(set['data'])
|
||||
else:
|
||||
print(_, set)
|
||||
data[set['label']] = np.empty((0,))
|
||||
|
||||
return data
|
Reference in New Issue
Block a user