| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 |
- from __future__ import division
- from __future__ import unicode_literals
- from __future__ import print_function
- from __future__ import absolute_import
- #from future import standard_library
- #standard_library.install_aliases()
- from builtins import input
- from builtins import str
- from builtins import *
- from builtins import object
- import os
- import matplotlib
- matplotlib.use('module://ipympl.backend_nbagg')
- #matplotlib.rcParams['text.latex.preamble'] = '\\usepackage{amsmath}'
- #matplotlib.rcParams['verbose.level'] = 'debug-annoying'
- import matplotlib.pyplot as mpl
- import matplotlib.transforms as transforms
- import numpy as np
- class MatplotlibDraw(object):
- """
- Simple interface for plotting. This interface makes use of
- Matplotlib for plotting.
- Some attributes that must be controlled directly (no set_* method
- since these attributes are changed quite seldom).
- ========================== ============================================
- Attribute Description
- ========================== ============================================
- allow_screen_graphics False means that no plot is shown on
- the screen. (Does not work yet.)
- arrow_head_width Size of arrow head.
- ========================== ============================================
- """
- line_colors = {'red': 'r', 'green': 'g', 'blue': 'b', 'cyan': 'c',
- 'magenta': 'm', 'purple': 'p',
- 'yellow': 'y', 'black': 'k', 'white': 'w',
- 'brown': 'brown', '': ''}
- def __init__(self):
- self.instruction_file = None
- self.allow_screen_graphics = True # does not work yet
- def __del__(self):
- if self.instruction_file:
- self.instruction_file.write('\nmpl.draw()\nraw_input()\n')
- self.instruction_file.close()
- def ok(self):
- """
- Return True if set_coordinate_system is called and
- objects can be drawn.
- """
- def adjust_coordinate_system(self, minmax, occupation_percent=80):
- """
- Given a dict of xmin, xmax, ymin, ymax values, and a desired
- filling of the plotting area of `occupation_percent` percent,
- set new axis limits.
- """
- x_range = minmax['xmax'] - minmax['xmin']
- y_range = minmax['ymax'] - minmax['ymin']
- new_x_range = x_range*100./occupation_percent
- x_space = new_x_range - x_range
- new_y_range = y_range*100./occupation_percent
- y_space = new_y_range - y_range
- self.ax.set_xlim(minmax['xmin']-x_space/2., minmax['xmax']+x_space/2.)
- self.ax.set_ylim(minmax['ymin']-y_space/2., minmax['ymax']+y_space/2.)
- def set_coordinate_system(self, xmin, xmax, ymin, ymax, axis=False,
- instruction_file=None, new_figure=True,
- xkcd=False):
- """
- Define the drawing area [xmin,xmax]x[ymin,ymax].
- axis: None or False means that axes with tickmarks
- are not drawn.
- instruction_file: name of file where all the instructions
- for the plotting program are stored (useful for debugging
- a figure or tailoring plots).
- """
- # Close file for previous figure and start new one
- # if not the figure file is the same
- if self.instruction_file is not None:
- if instruction_file == self.instruction_file.name:
- pass # continue with same file
- else:
- self.instruction_file.close() # make new py file for commands
- self.mpl = mpl
- if xkcd:
- self.mpl.xkcd()
- else:
- # Allow \boldsymbol{} etc in title, labels, etc
- matplotlib.rc('text', usetex=True)
- self.xmin, self.xmax, self.ymin, self.ymax = \
- float(xmin), float(xmax), float(ymin), float(ymax)
- self.xrange = self.xmax - self.xmin
- self.yrange = self.ymax - self.ymin
- self.axis = axis
- # Compute the right X11 geometry on the screen based on the
- # x-y ratio of axis ranges
- ratio = (self.ymax-self.ymin)/(self.xmax-self.xmin)
- self.xsize = 800 # pixel size
- self.ysize = self.xsize*ratio
- geometry = '%dx%d' % (self.xsize, self.ysize)
- # See http://stackoverflow.com/questions/7449585/how-do-you-set-the-absolute-position-of-figure-windows-with-matplotlib
- if isinstance(instruction_file, str):
- self.instruction_file = open(instruction_file, 'w')
- else:
- self.instruction_file = None
- self.mpl.ioff() # important for interactive drawing and animation
- if self.instruction_file:
- self.instruction_file.write("""\
- import matplotlib
- matplotlib.use('module://ipympl.backend_nbagg')
- # Allow \boldsymbol{} etc in title, labels, etc
- matplotlib.rc('text', usetex=True)
- #matplotlib.rcParams['text.latex.preamble'] = '\\usepackage{amsmath}'
- import matplotlib.pyplot as mpl
- import matplotlib.transforms as transforms
- mpl.ion() # for interactive drawing
- """)
- # Default properties
- self.set_linecolor('red')
- self.set_linewidth(2)
- self.set_linestyle('solid')
- self.set_filled_curves() # no filling
- self.set_fontsize(14)
- self.arrow_head_width = 0.2*self.xrange/16
- self._make_axes(new_figure=new_figure)
- manager = self.mpl.get_current_fig_manager()
- #manager.window.wm_geometry(geometry)
- def _make_axes(self, new_figure=False):
- if new_figure:
- self.fig = self.mpl.figure()
- self.ax = self.fig.gca()
- self.ax.set_xlim(self.xmin, self.xmax)
- self.ax.set_ylim(self.ymin, self.ymax)
- self.ax.set_aspect('equal') # extent of 1 unit is the same on the axes
- if not self.axis:
- self.mpl.axis('off')
- axis_cmd = "mpl.axis('off') # do not show axes with tickmarks\n"
- else:
- axis_cmd = ''
- if self.instruction_file:
- fig = 'fig = mpl.figure()\n' if new_figure else ''
- self.instruction_file.write("""\
- %s
- ax = fig.gca()
- xmin, xmax, ymin, ymax = %s, %s, %s, %s
- ax.set_xlim(xmin, xmax)
- ax.set_ylim(ymin, ymax)
- ax.set_aspect('equal')
- %s
- """ % (fig, self.xmin, self.xmax, self.ymin, self.ymax, axis_cmd))
- def inside(self, pt, exception=False):
- """Is point pt inside the defined plotting area?"""
- area = '[%s,%s]x[%s,%s]' % \
- (self.xmin, self.xmax, self.ymin, self.ymax)
- tol = 1E-14
- pt_inside = True
- if self.xmin - tol <= pt[0] <= self.xmax + tol:
- pass
- else:
- pt_inside = False
- if self.ymin - tol <= pt[1] <= self.ymax + tol:
- pass
- else:
- pt_inside = False
- if pt_inside:
- return pt_inside, 'point=%s is inside plotting area %s' % \
- (pt, area)
- else:
- msg = 'point=%s is outside plotting area %s' % (pt, area)
- if exception:
- raise ValueError(msg)
- return pt_inside, msg
- def set_linecolor(self, color):
- """
- Change the color of lines. Available colors are
- 'black', 'white', 'red', 'blue', 'green', 'yellow',
- 'magenta', 'cyan'.
- """
- self.linecolor = MatplotlibDraw.line_colors[color]
- def set_linestyle(self, style):
- """Change line style: 'solid', 'dashed', 'dashdot', 'dotted'."""
- if not style in ('solid', 'dashed', 'dashdot', 'dotted'):
- raise ValueError('Illegal line style: %s' % style)
- self.linestyle = style
- def set_linewidth(self, width):
- """Change the line width (int, starts at 1)."""
- self.linewidth = width
- def set_filled_curves(self, color='', pattern=''):
- """
- Fill area inside curves with specified color and/or pattern.
- A common pattern is '/' (45 degree lines). Other patterns
- include '-', '+', 'x', '\\', '*', 'o', 'O', '.'.
- """
- if color is False:
- self.fillcolor = ''
- self.fillpattern = ''
- else:
- self.fillcolor = color if len(color) == 1 else \
- MatplotlibDraw.line_colors[color]
- self.fillpattern = pattern
- def set_fontsize(self, fontsize=18):
- """
- Method for setting a common fontsize for text, unless
- individually specified when calling ``text``.
- """
- self.fontsize = fontsize
- def set_grid(self, on=False):
- self.mpl.grid(on)
- if self.instruction_file:
- self.instruction_file.write("\nmpl.grid(%s)\n" % str(on))
- def erase(self):
- """Erase the current figure."""
- self.mpl.delaxes()
- if self.instruction_file:
- self.instruction_file.write("\nmpl.delaxes() # erase\n")
- self._make_axes(new_figure=False)
- def plot_curve(self, x, y,
- linestyle=None, linewidth=None,
- linecolor=None, arrow=None,
- fillcolor=None, fillpattern=None,
- shadow=0, name=None):
- """Define a curve with coordinates x and y (arrays)."""
- #if not self.allow_screen_graphics:
- # mpl.ioff()
- #else:
- # mpl.ion()
- self.xdata = np.asarray(x, dtype=np.float)
- self.ydata = np.asarray(y, dtype=np.float)
- if linestyle is None:
- # use "global" linestyle
- linestyle = self.linestyle
- if linecolor is None:
- linecolor = self.linecolor
- if linewidth is None:
- linewidth = self.linewidth
- if fillcolor is None:
- fillcolor = self.fillcolor
- if fillpattern is None:
- fillpattern = self.fillpattern
- if shadow == 1:
- shadow = 3 # smallest displacement that is visible
- # We can plot fillcolor/fillpattern, arrow or line
- if self.instruction_file:
- import pprint
- if name is not None:
- self.instruction_file.write('\n# %s\n' % name)
- if not arrow:
- self.instruction_file.write(
- 'x = %s\n' % pprint.pformat(self.xdata.tolist()))
- self.instruction_file.write(
- 'y = %s\n' % pprint.pformat(self.ydata.tolist()))
- if fillcolor or fillpattern:
- if fillpattern != '':
- fillcolor = 'white'
- #print('%d coords, fillcolor="%s" linecolor="%s" fillpattern="%s"' % (x.size, fillcolor, linecolor, fillpattern))
- [line] = self.ax.fill(x, y, fillcolor, edgecolor=linecolor,
- linewidth=linewidth, hatch=fillpattern)
- if self.instruction_file:
- self.instruction_file.write("[line] = ax.fill(x, y, '%s', edgecolor='%s', linewidth=%d, hatch='%s')\n" % (fillcolor, linecolor, linewidth, fillpattern))
- else:
- # Plain line
- [line] = self.ax.plot(x, y, linecolor, linewidth=linewidth,
- linestyle=linestyle)
- if self.instruction_file:
- self.instruction_file.write("[line] = ax.plot(x, y, '%s', linewidth=%d, linestyle='%s')\n" % (linecolor, linewidth, linestyle))
- if arrow:
- # Note that a Matplotlib arrow is a line with the arrow tip
- if not arrow in ('->', '<-', '<->'):
- raise ValueError("arrow argument must be '->', '<-', or '<->', not %s" % repr(arrow))
- # Add arrow to first and/or last segment
- start = arrow == '<-' or arrow == '<->'
- end = arrow == '->' or arrow == '<->'
- if start:
- x_s, y_s = x[1], y[1]
- dx_s, dy_s = x[0]-x[1], y[0]-y[1]
- self._plot_arrow(x_s, y_s, dx_s, dy_s, '->',
- linestyle, linewidth, linecolor)
- if end:
- x_e, y_e = x[-2], y[-2]
- dx_e, dy_e = x[-1]-x[-2], y[-1]-y[-2]
- self._plot_arrow(x_e, y_e, dx_e, dy_e, '->',
- linestyle, linewidth, linecolor)
- if shadow:
- # http://matplotlib.sourceforge.net/users/transforms_tutorial.html#using-offset-transforms-to-create-a-shadow-effect
- # shift the object over 2 points, and down 2 points
- dx, dy = shadow/72., -shadow/72.
- offset = transforms.ScaledTranslation(
- dx, dy, self.fig.dpi_scale_trans)
- shadow_transform = self.ax.transData + offset
- # now plot the same data with our offset transform;
- # use the zorder to make sure we are below the line
- if linewidth is None:
- linewidth = 3
- self.ax.plot(x, y, linewidth=linewidth, color='gray',
- transform=shadow_transform,
- zorder=0.5*line.get_zorder())
- if self.instruction_file:
- self.instruction_file.write("""
- # Shadow effect for last ax.plot
- dx, dy = 3/72., -3/72.
- offset = matplotlib.transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
- shadow_transform = ax.transData + offset
- self.ax.plot(x, y, linewidth=%d, color='gray',
- transform=shadow_transform,
- zorder=0.5*line.get_zorder())
- """ % linewidth)
- def display(self, title=None, show=True):
- """Display the figure."""
- if title is not None:
- self.mpl.title(title)
- if self.instruction_file:
- self.instruction_file.write('mpl.title("%s")\n' % title)
- if show:
- self.mpl.draw()
- if self.instruction_file:
- self.instruction_file.write('mpl.draw()\n')
- def savefig(self, filename, dpi=None, crop=True):
- """Save figure in file. Set dpi=300 for really high resolution."""
- # If filename is without extension, generate all important formats
- ext = os.path.splitext(filename)[1]
- if not ext:
- # Create both PNG and PDF file
- self.mpl.savefig(filename + '.png', dpi=dpi)
- self.mpl.savefig(filename + '.pdf')
- if crop:
- # Crop the PNG file
- failure = os.system('convert -trim %s.png %s.png' %
- (filename, filename))
- if failure:
- print('convert from ImageMagick is not installed - needed for cropping PNG files')
- failure = os.system('pdfcrop %s.pdf %s.pdf' %
- (filename, filename))
- if failure:
- print('pdfcrop is not installed - needed for cropping PDF files')
- #self.mpl.savefig(filename + '.eps')
- if self.instruction_file:
- self.instruction_file.write('mpl.savefig("%s.png", dpi=%s)\n'
- % (filename, dpi))
- self.instruction_file.write('mpl.savefig("%s.pdf")\n'
- % filename)
- else:
- self.mpl.savefig(filename, dpi=dpi)
- if ext == '.png':
- if crop:
- failure = os.system('convert -trim %s %s' % (filename, filename))
- if failure:
- print('convert from ImageMagick is not installed - needed for cropping PNG files')
- elif ext == '.pdf':
- if crop:
- failure = os.system('pdfcrop %s %s' % (filename, filename))
- if failure:
- print('pdfcrop is not installed - needed for cropping PDF files')
- if self.instruction_file:
- self.instruction_file.write('mpl.savefig("%s", dpi=%s)\n'
- % (filename, dpi))
- def text(self, text, position, alignment='center', fontsize=0,
- arrow_tip=None, bgcolor=None, fgcolor=None, fontfamily=None):
- """
- Write `text` string at a position (centered, left, right - according
- to the `alignment` string). `position` is a point in the coordinate
- system.
- If ``arrow+tip != None``, an arrow is drawn from the text to a point
- (on a curve, for instance). The arrow_tip argument is then
- the (x,y) coordinates for the arrow tip.
- fontsize=0 indicates use of the default font as set by
- ``set_fontsize``.
- """
- if fontsize == 0:
- if hasattr(self, 'fontsize'):
- fontsize = self.fontsize
- else:
- raise AttributeError(
- 'No self.fontsize attribute to be used when text(...)\n'
- 'is called with fontsize=0. Call set_fontsize method.')
- kwargs = {}
- if fontfamily is not None:
- kwargs['family'] = fontfamily
- if bgcolor is not None:
- kwargs['backgroundcolor'] = bgcolor
- if fgcolor is not None:
- kwargs['color'] = fgcolor
- x, y = position
- if arrow_tip is None:
- self.ax.text(x, y, text, horizontalalignment=alignment,
- fontsize=fontsize, **kwargs)
- if self.instruction_file:
- self.instruction_file.write("""\
- ax.text(%g, %g, %s,
- horizontalalignment=%s, fontsize=%d)
- """ % (x, y, repr(text), repr(alignment), fontsize))
- else:
- if not len(arrow_tip) == 2:
- raise ValueError('arrow_tip=%s must be (x,y) pt.' % arrow)
- pt = arrow_tip
- self.ax.annotate(text, xy=pt, xycoords='data',
- textcoords='data', xytext=position,
- horizontalalignment=alignment,
- verticalalignment='top',
- fontsize=fontsize,
- arrowprops=dict(arrowstyle='->',
- facecolor='black',
- #linewidth=2,
- linewidth=1,
- shrinkA=5,
- shrinkB=5))
- if self.instruction_file:
- self.instruction_file.write("""\
- ax.annotate('%s', xy=%s, xycoords='data',
- textcoords='data', xytext=%s,
- horizontalalignment='%s',
- verticalalignment='top',
- fontsize=%d,
- arrowprops=dict(arrowstyle='->',
- facecolor='black',
- linewidth=2,
- shrinkA=5,
- shrinkB=5))
- """ % (text, pt.tolist() if isinstance(pt, np.ndarray) else pt,
- position, alignment, fontsize))
- # Drawing annotations with arrows:
- #http://matplotlib.sourceforge.net/users/annotations_intro.html
- #http://matplotlib.sourceforge.net/mpl_examples/pylab_examples/annotation_demo2.py
- #http://matplotlib.sourceforge.net/users/annotations_intro.html
- #http://matplotlib.sourceforge.net/users/annotations_guide.html#plotting-guide-annotation
- def _plot_arrow(self, x, y, dx, dy, style='->',
- linestyle=None, linewidth=None, linecolor=None):
- """Draw arrow (dx,dy) at (x,y). `style` is '->', '<-' or '<->'."""
- if linestyle is None:
- # use "global" linestyle
- linestyle = self.linestyle
- if linecolor is None:
- linecolor = self.linecolor
- if linewidth is None:
- linewidth = self.linewidth
- if style == '->' or style == '<->':
- self.mpl.arrow(x, y, dx, dy,
- #hold=True,
- facecolor=linecolor,
- edgecolor=linecolor,
- linestyle=linestyle,
- linewidth=linewidth,
- head_width=self.arrow_head_width,
- #head_width=0.1,
- #width=1, # width of arrow body in coordinate scale
- length_includes_head=True,
- shape='full')
- if self.instruction_file:
- self.instruction_file.write("""\
- mpl.arrow(x=%g, y=%g, dx=%g, dy=%g,
- facecolor='%s', edgecolor='%s',
- linestyle='%s',
- linewidth=%g, head_width=0.1,
- length_includes_head=True,
- shape='full')
- """ % (x, y, dx, dy, linecolor, linecolor, linestyle, linewidth))
- if style == '<-' or style == '<->':
- self.mpl.arrow(x+dx, y+dy, -dx, -dy, hold=True,
- facecolor=linecolor,
- edgecolor=linecolor,
- linewidth=linewidth,
- head_width=0.1,
- #width=1,
- length_includes_head=True,
- shape='full')
- if self.instruction_file:
- self.instruction_file.write("""\
- mpl.arrow(x=%g, y=%g, dx=%g, dy=%g,
- facecolor='%s', edgecolor='%s',
- linewidth=%g, head_width=0.1,
- length_includes_head=True,
- shape='full')
- """ % (x+dx, y+dy, -dx, -dy, linecolor, linecolor, linewidth))
- def arrow2(self, x, y, dx, dy, style='->'):
- """Draw arrow (dx,dy) at (x,y). `style` is '->', '<-' or '<->'."""
- self.ax.annotate('', xy=(x+dx,y+dy), xytext=(x,y),
- arrowprops=dict(arrowstyle=style,
- facecolor='black',
- linewidth=1,
- shrinkA=0,
- shrinkB=0))
- if self.instruction_file:
- self.instruction_file.write("""
- ax.annotate('', xy=(%s,%s), xytext=(%s,%s),
- arrowprops=dict(arrowstyle=%s,
- facecolor='black',
- linewidth=1,
- shrinkA=0,
- shrinkB=0))
- """ % (x+dx, y+dy, x, y, style))
- def _test():
- d = MatplotlibDraw(0, 10, 0, 5, instruction_file='tmp3.py', axis=True)
- d.set_linecolor('magenta')
- d.set_linewidth(6)
- # triangle
- x = np.array([1, 4, 1, 1]); y = np.array([1, 1, 4, 1])
- d.set_filled_curves('magenta')
- d.plot_curve(x, y)
- d.set_filled_curves(False)
- d.plot_curve(x+4, y)
- d.text('some text1', position=(8,4), arrow_tip=(6, 1), alignment='left',
- fontsize=18)
- pos = np.array((7,4.5)) # numpy points work fine
- d.text('some text2', position=pos, arrow_tip=(6, 1), alignment='center',
- fontsize=12)
- d.set_linewidth(2)
- d.arrow(0.25, 0.25, 0.45, 0.45)
- d.arrow(0.25, 0.25, 0.25, 4, style='<->')
- d.arrow2(4.5, 0, 0, 3, style='<->')
- x = np.linspace(0, 9, 201)
- y = 4.5 + 0.45*np.cos(0.5*np.pi*x)
- d.plot_curve(x, y, arrow='end')
- d.display()
- input()
- if __name__ == '__main__':
- _test()
|