""" This generate diagram in .png and .svg from neo.core Author: sgarcia """ from datetime import datetime import numpy as np import quantities as pq from matplotlib import pyplot from matplotlib.patches import Rectangle, ArrowStyle, FancyArrowPatch from matplotlib.font_manager import FontProperties from neo.test.generate_datasets import fake_neo line_heigth = .22 fontsize = 10.5 left_text_shift = .1 dpi = 100 def get_rect_height(name, obj): ''' calculate rectangle height ''' nlines = 1.5 nlines += len(getattr(obj, '_all_attrs', [])) nlines += len(getattr(obj, '_single_child_objects', [])) nlines += len(getattr(obj, '_multi_child_objects', [])) nlines += len(getattr(obj, '_multi_parent_objects', [])) return nlines * line_heigth def annotate(ax, coord1, coord2, connectionstyle, color, alpha): arrowprops = dict(arrowstyle='fancy', # ~ patchB=p, shrinkA=.3, shrinkB=.3, fc=color, ec=color, connectionstyle=connectionstyle, alpha=alpha) bbox = dict(boxstyle="square", fc="w") a = ax.annotate('', coord1, coord2, # xycoords="figure fraction", # textcoords="figure fraction", ha="right", va="center", size=fontsize, arrowprops=arrowprops, bbox=bbox) a.set_zorder(-4) def calc_coordinates(pos, height): x = pos[0] y = pos[1] + height - line_heigth * .5 return pos[0], y def generate_diagram(filename, rect_pos, rect_width, figsize): rw = rect_width fig = pyplot.figure(figsize=figsize) ax = fig.add_axes([0, 0, 1, 1]) all_h = {} objs = {} for name in rect_pos: objs[name] = fake_neo(name) all_h[name] = get_rect_height(name, objs[name]) # draw connections color = ['c', 'm', 'y'] alpha = [1., 1., 0.3] for name, pos in rect_pos.items(): obj = objs[name] relationships = [getattr(obj, '_single_child_objects', []), getattr(obj, '_multi_child_objects', []), getattr(obj, '_child_properties', [])] for r in range(3): for ch_name in relationships[r]: x1, y1 = calc_coordinates(rect_pos[ch_name], all_h[ch_name]) x2, y2 = calc_coordinates(pos, all_h[name]) if r in [0, 2]: x2 += rect_width connectionstyle = "arc3,rad=-0.2" elif y2 >= y1: connectionstyle = "arc3,rad=0.7" else: connectionstyle = "arc3,rad=-0.7" annotate(ax=ax, coord1=(x1, y1), coord2=(x2, y2), connectionstyle=connectionstyle, color=color[r], alpha=alpha[r]) # draw boxes for name, pos in rect_pos.items(): htotal = all_h[name] obj = objs[name] allrelationship = (list(getattr(obj, '_child_containers', [])) + list(getattr(obj, '_multi_parent_containers', []))) rect = Rectangle(pos, rect_width, htotal, facecolor='w', edgecolor='k', linewidth=2.) ax.add_patch(rect) # title green pos2 = pos[0], pos[1] + htotal - line_heigth * 1.5 rect = Rectangle(pos2, rect_width, line_heigth * 1.5, facecolor='g', edgecolor='k', alpha=.5, linewidth=2.) ax.add_patch(rect) # single relationship relationship = getattr(obj, '_single_child_objects', []) pos2 = pos[1] + htotal - line_heigth * (1.5 + len(relationship)) rect_height = len(relationship) * line_heigth rect = Rectangle((pos[0], pos2), rect_width, rect_height, facecolor='c', edgecolor='k', alpha=.5) ax.add_patch(rect) # multi relationship relationship = (list(getattr(obj, '_multi_child_objects', [])) + list(getattr(obj, '_multi_parent_containers', []))) pos2 = (pos[1] + htotal - line_heigth * (1.5 + len(relationship)) - rect_height) rect_height = len(relationship) * line_heigth rect = Rectangle((pos[0], pos2), rect_width, rect_height, facecolor='m', edgecolor='k', alpha=.5) ax.add_patch(rect) # necessary attr pos2 = (pos[1] + htotal - line_heigth * (1.5 + len(allrelationship) + len(obj._necessary_attrs))) rect = Rectangle((pos[0], pos2), rect_width, line_heigth * len(obj._necessary_attrs), facecolor='r', edgecolor='k', alpha=.5) ax.add_patch(rect) # name if hasattr(obj, '_quantity_attr'): post = '* ' else: post = '' ax.text(pos[0] + rect_width / 2., pos[1] + htotal - line_heigth * 1.5 / 2., name + post, horizontalalignment='center', verticalalignment='center', fontsize=fontsize + 2, fontproperties=FontProperties(weight='bold'), ) # relationship for i, relat in enumerate(allrelationship): ax.text(pos[0] + left_text_shift, pos[1] + htotal - line_heigth * (i + 2), relat + ': list', horizontalalignment='left', verticalalignment='center', fontsize=fontsize, ) # attributes for i, attr in enumerate(obj._all_attrs): attrname, attrtype = attr[0], attr[1] t1 = attrname if (hasattr(obj, '_quantity_attr') and obj._quantity_attr == attrname): t1 = attrname + '(object itself)' else: t1 = attrname if attrtype == pq.Quantity: if attr[2] == 0: t2 = 'Quantity scalar' else: t2 = 'Quantity %dD' % attr[2] elif attrtype == np.ndarray: t2 = "np.ndarray %dD dt='%s'" % (attr[2], attr[3].kind) elif attrtype == datetime: t2 = 'datetime' else: t2 = attrtype.__name__ t = t1 + ' : ' + t2 ax.text(pos[0] + left_text_shift, pos[1] + htotal - line_heigth * (i + len(allrelationship) + 2), t, horizontalalignment='left', verticalalignment='center', fontsize=fontsize, ) xlim, ylim = figsize ax.set_xlim(0, xlim) ax.set_ylim(0, ylim) ax.set_xticks([]) ax.set_yticks([]) fig.savefig(filename, dpi=dpi) def generate_diagram_simple(): figsize = (18, 12) rw = rect_width = 3. bf = blank_fact = 1.2 rect_pos = {'Block': (.5 + rw * bf * 0, 4), 'Segment': (.5 + rw * bf * 1, .5), 'Event': (.5 + rw * bf * 4, 3.0), 'Epoch': (.5 + rw * bf * 4, 1.0), 'Group': (.5 + rw * bf * 1, 7.5), 'ChannelView': (.5 + rw * bf * 2., 9.9), 'SpikeTrain': (.5 + rw * bf * 3, 7.5), 'IrregularlySampledSignal': (.5 + rw * bf * 3, 0.5), 'AnalogSignal': (.5 + rw * bf * 3, 4.9), } # todo: add ImageSequence, RegionOfInterest generate_diagram('simple_generated_diagram.svg', rect_pos, rect_width, figsize) generate_diagram('simple_generated_diagram.png', rect_pos, rect_width, figsize) if __name__ == '__main__': generate_diagram_simple() pyplot.show()