From f40f2badd8068adccfcfdd17530ef11b0d48a809 Mon Sep 17 00:00:00 2001 From: sebastiankloth Date: Mon, 11 Apr 2022 11:01:13 +0200 Subject: [PATCH] Initial project version --- .gitignore | 10 + LICENSE | 21 ++ README.md | 36 ++++ pygrace/README.md | 49 +++++ pygrace/__init__.py | 1 + pygrace/_load_stuff.py | 114 ++++++++++ pygrace/colors.py | 158 ++++++++++++++ pygrace/pygrace.py | 355 +++++++++++++++++++++++++++++++ pygrace/str2format.py | 201 ++++++++++++++++++ pygrace/tex2grace.py | 37 ++++ setup.py | 13 ++ tudplot/__init__.py | 77 +++++++ tudplot/altair.py | 157 ++++++++++++++ tudplot/tex2grace.py | 53 +++++ tudplot/tud.mplstyle | 7 + tudplot/tud.py | 42 ++++ tudplot/utils.py | 220 ++++++++++++++++++++ tudplot/xmgrace.py | 461 +++++++++++++++++++++++++++++++++++++++++ 18 files changed, 2012 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 pygrace/README.md create mode 100644 pygrace/__init__.py create mode 100644 pygrace/_load_stuff.py create mode 100644 pygrace/colors.py create mode 100644 pygrace/pygrace.py create mode 100644 pygrace/str2format.py create mode 100644 pygrace/tex2grace.py create mode 100644 setup.py create mode 100755 tudplot/__init__.py create mode 100755 tudplot/altair.py create mode 100755 tudplot/tex2grace.py create mode 100755 tudplot/tud.mplstyle create mode 100644 tudplot/tud.py create mode 100644 tudplot/utils.py create mode 100755 tudplot/xmgrace.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..984d011 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +doc/_build +.idea +__pycache__ +build/ +dist/ +*.egg-info +logarithmic.*.so +logarithmic.c +coordinates.*.so +coordinates.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..314322f --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3cd3c0 --- /dev/null +++ b/README.md @@ -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') diff --git a/pygrace/README.md b/pygrace/README.md new file mode 100644 index 0000000..b7f40bf --- /dev/null +++ b/pygrace/README.md @@ -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 + \ No newline at end of file diff --git a/pygrace/__init__.py b/pygrace/__init__.py new file mode 100644 index 0000000..b91cf25 --- /dev/null +++ b/pygrace/__init__.py @@ -0,0 +1 @@ +from .pygrace import saveagr \ No newline at end of file diff --git a/pygrace/_load_stuff.py b/pygrace/_load_stuff.py new file mode 100644 index 0000000..679a784 --- /dev/null +++ b/pygrace/_load_stuff.py @@ -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) + diff --git a/pygrace/colors.py b/pygrace/colors.py new file mode 100644 index 0000000..11a534c --- /dev/null +++ b/pygrace/colors.py @@ -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 diff --git a/pygrace/pygrace.py b/pygrace/pygrace.py new file mode 100644 index 0000000..54f29f3 --- /dev/null +++ b/pygrace/pygrace.py @@ -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)) diff --git a/pygrace/str2format.py b/pygrace/str2format.py new file mode 100644 index 0000000..8bbf14e --- /dev/null +++ b/pygrace/str2format.py @@ -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': '
', + '*': 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'{0:}'.format(s[2:-1])) + for s in sub_pattern_2.findall(text): + text = text.replace(s, u'{0:}'.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'{0:}'.format(s.strip()[2:-1])) + for s in sup_pattern_2.findall(text): + text = text.replace(s, u'{0:}'.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 [('', '_{'), ('', '}'), ('', '^{'), ('', '}')]: + 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))))) diff --git a/pygrace/tex2grace.py b/pygrace/tex2grace.py new file mode 100644 index 0000000..436ce12 --- /dev/null +++ b/pygrace/tex2grace.py @@ -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 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..42669b9 --- /dev/null +++ b/setup.py @@ -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, +) diff --git a/tudplot/__init__.py b/tudplot/__init__.py new file mode 100755 index 0000000..25f6b82 --- /dev/null +++ b/tudplot/__init__.py @@ -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)') diff --git a/tudplot/altair.py b/tudplot/altair.py new file mode 100755 index 0000000..1da9739 --- /dev/null +++ b/tudplot/altair.py @@ -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')) diff --git a/tudplot/tex2grace.py b/tudplot/tex2grace.py new file mode 100755 index 0000000..c9a3c25 --- /dev/null +++ b/tudplot/tex2grace.py @@ -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 diff --git a/tudplot/tud.mplstyle b/tudplot/tud.mplstyle new file mode 100755 index 0000000..3778f47 --- /dev/null +++ b/tudplot/tud.mplstyle @@ -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 \ No newline at end of file diff --git a/tudplot/tud.py b/tudplot/tud.py new file mode 100644 index 0000000..8df1133 --- /dev/null +++ b/tudplot/tud.py @@ -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)] diff --git a/tudplot/utils.py b/tudplot/utils.py new file mode 100644 index 0000000..79913fa --- /dev/null +++ b/tudplot/utils.py @@ -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 + diff --git a/tudplot/xmgrace.py b/tudplot/xmgrace.py new file mode 100755 index 0000000..4594a49 --- /dev/null +++ b/tudplot/xmgrace.py @@ -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