Source code for scitools.avplotter

"""
avplotter ("ascii vertical plotter") is a simple ASCII plotter for
curve plots, where the x axis points downward and the y axis
is horizontal. The plot is realized by printing it line by line.
There are two main applications: 1) very long time series, and
2) plots that would be convenient to have as pure text.

See the documentation of class Plotter for examples of various
types of plots.
"""

[docs]class Plotter: """ ASCII plotter with x axis downwards and y axis horizontal. Can make a plot by writing out new x values line by line in a terminal window or a file. Very suited for long time series. Example: >>> a = 0.2 >>> p = Plotter(-1-a, 1+a, width=50) >>> from math import sin, pi >>> from numpy import linspace >>> num_periods = 2 >>> resolution_per_period = 22 >>> tp = linspace(0, num_periods*2*pi, ... num_periods*resolution_per_period + 1) >>> for t in tp: ... y = (1 + a*sin(0.5*t))*sin(t) ... print 't=%5.2f' % t, p.plot(t, y), '%5.2f' % y ... t= 0.00 | 0.00 t= 0.29 | * 0.29 t= 0.57 | * 0.57 t= 0.86 | * 0.82 t= 1.14 | * 1.01 t= 1.43 | * 1.12 t= 1.71 | * 1.14 t= 2.00 | * 1.06 t= 2.28 | * 0.89 t= 2.57 | * 0.64 t= 2.86 | * 0.34 t= 3.14 | 0.00 t= 3.43 * | -0.34 t= 3.71 * | -0.64 t= 4.00 * | -0.89 t= 4.28 * | -1.06 t= 4.57 * | -1.14 t= 4.86 * | -1.12 t= 5.14 * | -1.01 t= 5.43 * | -0.82 t= 5.71 * | -0.57 t= 6.00 * | -0.29 t= 6.28 | -0.00 t= 6.57 | * 0.27 t= 6.85 | * 0.51 t= 7.14 | * 0.69 t= 7.43 | * 0.81 t= 7.71 | * 0.86 t= 8.00 | * 0.84 t= 8.28 | * 0.76 t= 8.57 | * 0.62 t= 8.85 | * 0.44 t= 9.14 | * 0.23 t= 9.42 | 0.00 t= 9.71 * | -0.23 t=10.00 * | -0.44 t=10.28 * | -0.62 t=10.57 * | -0.76 t=10.85 * | -0.84 t=11.14 * | -0.86 t=11.42 * | -0.81 t=11.71 * | -0.69 t=12.00 * | -0.51 t=12.28 * | -0.27 t=12.57 | -0.00 Here is a one-dimensional random walk example:: import time, numpy as np p = Plotter(-1, 1, width=75) np.random.seed(10) y = 0 while True: random_step = 1 if np.random.random() > 0.5 else -1 y = y + 0.05*random_step if y < -1: print 'HOME!!!!!!!!!' break print p.plot(0, y) try: time.sleep(0.1) except KeyboardInterrupt: print 'Interrupted by Ctrl-C' break One can easily plot two or more curves side by side. Here we plot two curves (sine and cosine), each with a width of 25 characters:: p_sin = Plotter(-1, 1, width=25, symbols='s') p_cos = Plotter(-1, 1, width=25, symbols='c') from math import sin, cos, pi from numpy import linspace tp = linspace(0, 6*pi, 6*8+1) for t in tp: print p_sin.plot(t, sin(t)), p_cos.plot(t, cos(t)) The output reads:: | | c | s | c | s | c | s | c | s | | s c | | s c | | s c | | c | s | c | s | c | s | c | s | c| s | | c s | | c s | | c s| | c | s | c | s | c | s | c | s | | s c | | s c | | s c | | c | s | c | s | c | s | c | s | c| s | | c s | | c s | | c s| | c | s | c | s | c | s | c | s | | s c | | s c | | s c | | c | s | c | s | c | s | c | s | c| s | | c s | | c s | | c s| | c Alternatively, two curves (here sine and cosine) can be plotted in the same coordinate system:: p = Plotter(-1, 1, width=50, symbols='sc') from math import sin, cos, pi from numpy import linspace tp = linspace(0, 6*pi, 6*8+1) for t in tp: print p.plot(t, sin(t), cos(t)) The output from this code becomes:: | c | s c | c | c s | s c | s c | s c | s c | c s | c | s c | s | s | c s | c s | c | c | s c | c | c s | s c | s c | s c | s c | c s | c | s c | s | s | c s | c s | c | c | s c | c | c s | s c | s c | s c | s c | c s | c | s c | s | s | c s | c s | c | c """
[docs] def __init__(self, ymin, ymax, width=68, symbols='*o+x@', vertical_line=0): """ Create a line by line plotter with the x axis pointing downward. The `ymin` and `ymax` variables define the extent of the y axis. The `width` parameter is the number of characters used for the y domain (axis). The symbols used for curves are given by the `symbols` string (first symbol, by default is ``*``, next is ``o``). The `vertical_line` parameter specifies for which y value where the x axis is drawn (y=0 by default). """ self.yaxis = float(ymin), float(ymax) self.width = width self.symbols = symbols self.vertical_line = vertical_line
def _map(self, y): """Return the column no. corresponding to y.""" ymin, ymax = self.yaxis if y < ymin: self.too_small = True self.too_large = False c = 0 elif y > ymax: self.too_small = False self.too_large = True c = -1 else: self.too_small = self.too_large = False y_in_01 = (y-ymin)/(ymax - ymin) c = int(round(y_in_01*self.width)) return c
[docs] def plot(self, x, *y, **kwargs): """ Return next line in plot, given x and some y values. Supported kwargs: print_out_of_range_value: if True, print the value if it is out of range. """ print_out_of_range_value = \ kwargs.get('print_out_of_range_value', True) line = [' ']*(self.width + 1) y_value = '' for yi, symbol in zip(y, self.symbols): c = self._map(yi) if self.too_small or self.too_large: symbol = '|' if print_out_of_range_value: y_value = '%.1E' % yi else: line[c] = symbol # Mark 'x' axis if self.yaxis[0] < self.vertical_line and \ self.yaxis[1] > self.vertical_line: c = self._map(0) line[c] = '|' return ''.join(line) + y_value
[docs]def plot(*args, **kwargs): """ Easyviz-style plot command. args holds x1, y1, x2, y2, ...:: plot(t, u1, t, u2, axis=[0, 10, -1, 1]) No other keyword arguments has any effect. """ if 'axis' in kwargs: ymin, ymax = kwargs['axis'][2:] else: ymin = 1E+20 ymax = -ymin for i in range(1,len(args),2): ymin = max(ymin, args[i].min()) ymax = max(ymax, args[i].max()) p = Plotter(ymin, ymax, width=70) num_curves = len(args)/2 if num_curves > 4: raise ValueError('avplotter.plot: cannot plot more than 4 curves') x_length = len(args[0]) for i in range(2,len(args),2): if len(args[i]) != x_length: raise ValueError('avplotter.plot: all x coordinates for all curves must have the same length (%d vs %d)' % (len(args[i]), x_length)) x_array = args[0] for i, x in enumerate(x_array): try: y = [args[j][i] for j in range(1,len(args),2)] except IndexError: raise ValueError('index %d in x_array is illegal in args[%d] (length=%d)' % (i, j, len(args[j]))) print p.plot(x_array, *y)
[docs]def test_sin(): a = 0.2 p = Plotter(-1-a, 1+a, width=50) from math import sin, pi from numpy import linspace num_periods = 2 resolution_per_period = 22 s = '' tp = linspace(0, num_periods*2*pi, num_periods*resolution_per_period + 1) for t in tp: y = (1 + a*sin(0.5*t))*sin(t) s += 't=%5.2f %s %5.2f\n' % (t, p.plot(t, y), y) ans = """\ t= 0.00 | 0.00 t= 0.29 | * 0.29 t= 0.57 | * 0.57 t= 0.86 | * 0.82 t= 1.14 | * 1.01 t= 1.43 | * 1.12 t= 1.71 | * 1.14 t= 2.00 | * 1.06 t= 2.28 | * 0.89 t= 2.57 | * 0.64 t= 2.86 | * 0.34 t= 3.14 | 0.00 t= 3.43 * | -0.34 t= 3.71 * | -0.64 t= 4.00 * | -0.89 t= 4.28 * | -1.06 t= 4.57 * | -1.14 t= 4.86 * | -1.12 t= 5.14 * | -1.01 t= 5.43 * | -0.82 t= 5.71 * | -0.57 t= 6.00 * | -0.29 t= 6.28 | -0.00 t= 6.57 | * 0.27 t= 6.85 | * 0.51 t= 7.14 | * 0.69 t= 7.43 | * 0.81 t= 7.71 | * 0.86 t= 8.00 | * 0.84 t= 8.28 | * 0.76 t= 8.57 | * 0.62 t= 8.85 | * 0.44 t= 9.14 | * 0.23 t= 9.42 | 0.00 t= 9.71 * | -0.23 t=10.00 * | -0.44 t=10.28 * | -0.62 t=10.57 * | -0.76 t=10.85 * | -0.84 t=11.14 * | -0.86 t=11.42 * | -0.81 t=11.71 * | -0.69 t=12.00 * | -0.51 t=12.28 * | -0.27 t=12.57 | -0.00 """ assert _compare(ans, s)
[docs]def test_2_curves_v1(): p_sin = Plotter(-1, 1, width=25, symbols='s') p_cos = Plotter(-1, 1, width=25, symbols='c') from math import sin, cos, pi from numpy import linspace tp = linspace(0, 6*pi, 6*8+1) s = '' for t in tp: s += '%s %s\n' % (p_sin.plot(t, sin(t)), p_cos.plot(t, cos(t))) ans = """\ | | c | s | c | s | c | s | c | s | | s c | | s c | | s c | | c | s | c | s | c | s | c | s | c| s | | c s | | c s | | c s| | c | s | c | s | c | s | c | s | | s c | | s c | | s c | | c | s | c | s | c | s | c | s | c| s | | c s | | c s | | c s| | c | s | c | s | c | s | c | s | | s c | | s c | | s c | | c | s | c | s | c | s | c | s | c| s | | c s | | c s | | c s| | c """ assert _compare(ans, s)
[docs]def test_2_curves_v2(): p = Plotter(-1, 1, width=50, symbols='sc') from math import sin, cos, pi from numpy import linspace tp = linspace(0, 6*pi, 6*8+1) s = '' for t in tp: s += '%s\n' % (p.plot(t, sin(t), cos(t))) ans = """\ | c | s c | c | c s | s c | s c | s c | s c | c s | c | s c | s | s | c s | c s | c | c | s c | c | c s | s c | s c | s c | s c | c s | c | s c | s | s | c s | c s | c | c | s c | c | c s | s c | s c | s c | s c | c s | c | s c | s | s | c s | c s | c | c """ assert _compare(ans, s)
[docs]def test_random_walk(): import time, numpy as np p = Plotter(-1, 1, width=75) np.random.seed(10) y = 0 s = '' while True: random_step = 1 if np.random.random() > 0.5 else -1 y = y + 0.05*random_step if y < -1: break s += '%s\n' % (p.plot(0, y)) # t is just dummy ans = """\ |* | |* | * |* | * | | * | * | * | | * | | |* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |* | * |* | |* | * | * | * | * | * | * | * | * | * |* | |* | * |* | * | | * | * | * | * | * | * | * | | * | * | * | | |* | * | | * | | |* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | | |* | * |* | |* | * | * | * | | * | | |* | * |* | * | * | * | * | * | * | * | * | * | * | * | | |* | * | * | * | * | * |* | * |* | * |* | |* | * |* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | | * | | |* | |* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | | |* | * | * | * | * | * | * | * | * | * | | * | * | * | * | * | * | * | * | * | * | * | * | * | | * | * | * | * | * | | |* | * | * | * | | * | | |* | |* | * | * | * |* | |* | * | | * | * | * | | * | * | * | * | * | * | * | | |* | |* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | 1.0E+00 | 1.1E+00 | 1.1E+00 | 1.1E+00 | 1.0E+00 | * | * | * | * | * | 1.0E+00 | 1.1E+00 | 1.0E+00 | 1.1E+00 | 1.1E+00 | 1.1E+00 | 1.0E+00 | * | 1.0E+00 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |* *| * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | *| |* | * | * | * | * | * | * | * | * | * |* | * |* *| * | * | * | * | * | * | * | * | * | * | * | *| |* *| * | *| * | *| |* *| * | * | * | * | * | * | * | *| * | *| * | *| |* *| |* *| * | *| |* | * | * | * | * | * | * | * |* | * |* | * |* | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | 1.0E+00 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | 1.0E+00 | 1.1E+00 | 1.0E+00 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |* | * |* *| * | * | * | *| |* | * |* *| * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | """ assert _compare(ans, s)
[docs]def run_random_walk(): import time, numpy as np p = Plotter(-1, 1, width=75) np.random.seed(10) y = 0 while True: random_step = 1 if np.random.random() > 0.5 else -1 y = y + 0.05*random_step if y < -1: print 'HOME!!!' break print p.plot(0, y) try: time.sleep(0.1) except KeyboardInterrupt: print 'Interrupted by Ctrl-C' break
def _compare(ans, s): for line1, line2 in zip(ans.splitlines(), s.splitlines()): if line1.strip() != line2.strip(): return False return True if __name__ == '__main__': test_sin(); test_2_curves_v1(); test_2_curves_v2(); test_random_walk() import sys try: if sys.argv[1] == 'random_walk': run_random_walk() except: pass