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