#!/usr/bin/env python from plotext._utility import all_markers, colors import argparse, sys, os import plotext as plt try: import shtab except ImportError: from . import _shtab as shtab # For Possible Colors and Markers Completion def dict_to_complete(d={}): return {'zsh': '((' + ' '.join(map(lambda x: str(x[0]) + '\\:' + x[1], d.items())) + '))'} def list_to_complete(data): return {'zsh': '((' + ' '.join([el + '\\:' for el in data]) + '))'} def build_parser(): examples = """Access each function documentation for further guidance, eg: plotext scatter -h""" parser = argparse.ArgumentParser( prog = "plotext", description = "plotting directly on terminal", epilog = examples, formatter_class = argparse.RawDescriptionHelpFormatter) shtab.add_argument_to(parser, ["-s", "--print-completion"]) parser.set_defaults(type = "scatter") path_parser = argparse.ArgumentParser(add_help = False) path_parser.add_argument("-p", "--path", action = 'store', #dest = 'path', type = str, metavar = "FILE", help = "file path of the data table; if not used it will read from stdin. Use 'test' to automatically download, in your user folder, some test data/image/gif or video, depending on the function used; the file will be removed after the plot", ).complete = shtab.FILE path_parser.add_argument("-r", "--first-row", action = "store", type = int, default = 0, metavar = "FIRST-ROW", help = "The first line to consider in the data file (counting from 0).") common_parser = argparse.ArgumentParser(add_help = False) common_parser.add_argument("-clt", "--clear_terminal", nargs = 1, type = str, default = ["False"], choices = ["True", "False"], metavar = "BOOL", help = "it clears the terminal before plotting, if True (as by default)") common_parser.add_argument("-s", "--sleep", nargs = 1, type = float, default = [0], help = "it adds a sleeping time after plotting, to reduce flickering when multiple plots are required; 0 by default") data_parser = argparse.ArgumentParser(add_help = False) data_parser.add_argument("-xcol", "--xcolumn", nargs = "+", # 1 or more type = str, default = ["none"], help = "the column number (starting from 1), in the data table, that will be used as x data; by default 'none'") data_parser.add_argument("-ycols", "--ycolumns", nargs = "+", # 1 or more type = str, default = ["all"], help = "the column numbers (starting from 1), in the data table, that will be used as y data; by default 'all'") data_parser.add_argument("-d", "--delimiter", type = str, default = [' '], nargs = 1, help = "character to be used to separate columns in the data table (eg: ;); by default the white space ' '") data_parser.add_argument("-l", "--lines", nargs = "+", # 1 or more type = int, default = [1000], help = "number of lines, from data table, to be plotted at each iteration; 1000 by default; data shorter then this will be plotted in a single iteration") options_parser = argparse.ArgumentParser(add_help = False) options_parser.add_argument("-m", "--marker", type = str, default = ['hd'], nargs = 1, choices = all_markers.keys(), metavar = "marker", help = "character or marker code identifying the data points in the plot, eg: x, braille, sd, heart, fhd; by default hd", ).complete = dict_to_complete(all_markers) options_parser.add_argument("-c", "--color", type = str, default = [None], nargs = 1, choices = colors, metavar = "COLOR", help = "color of the data points in the plot, between: " + ",".join(colors) + "; add + at the end of the color (except black and white) for clearer colors, eg: red+" ).complete = list_to_complete(colors) options_parser.add_argument("-t", "--title", nargs = 1, type = str, default = [None], help = "the plot title") options_parser.add_argument("-xl", "--xlabel", nargs = 1, type = str, default = [None], help = "the label of the x axis") options_parser.add_argument("-yl", "--ylabel", nargs = 1, type = str, default = [None], help = "the label of the y axis") options_parser.add_argument("-g", "--grid", nargs = 1, type = str, default = ["False"], choices = ["True", "False"], metavar = "BOOL", help = "it add grid lines if True, or removed them if False (as by default)") barhist_parser = argparse.ArgumentParser(add_help = False) barhist_parser.add_argument("-o", "--orientation", nargs = 1, type = str, default = ['v'], choices = ['v', 'h'], metavar = "ORIENTATION", help = "bar orientation, v for vertical (as by default), h for horizontal") barhist_parser.add_argument("-f", "--fill", nargs = 1, type = str, default = ["True"], choices = ["True", "False"], metavar = "BOOL", help = "it fills the bars with colored markers if True (as by default), otherwise only the bars skeleton is shown") subparser = parser.add_subparsers(dest = 'type', help = 'the user defined plot type') scatter = subparser.add_parser('scatter', description = "plots a series of data points", parents = [path_parser, data_parser, common_parser, options_parser], help = 'plots a series of data points', epilog = "eg: plotext scatter --path test --xcolumn 1 --ycolumns 2 --lines 5000 --title 'Scatter Plot Test' --marker braille") plot = subparser.add_parser('plot', parents = [path_parser, data_parser, common_parser, options_parser], description = "plots lines between consecutive data points", help = 'plots lines between consecutive data points', epilog = "eg: plotext plot --path test --xcolumn 1 --ycolumns 2 --sleep 0.1 --lines 2500 --clear_terminal True --color magenta+ --title 'Plot Test'") plotter = subparser.add_parser('plotter', parents = [path_parser, data_parser, common_parser, options_parser], description = 'plots a series of data points and the lines between consecutive ones', help = 'scatter + plot', epilog = "eg:plotext plotter --path test --xcolumn 1 --ycolumns 2 --sleep 0.1 --lines 120 --clear_terminal True --marker hd --title 'Plotter Test'") bar = subparser.add_parser('bar', parents = [path_parser, data_parser, common_parser, options_parser, barhist_parser], description = 'builds a bar plot', help = 'bar plot', epilog = "eg: plotext bar --path test --xcolumn 1 --title 'Bar Plot Test' --xlabel Animals --ylabel Count") bar.add_argument("-w", "--width", nargs = 1, type = float, default = [None], help = "bars width as a float between 0 and 1") hist = subparser.add_parser('hist', parents = [path_parser, data_parser, common_parser, options_parser, barhist_parser], description = 'builds a histogram plot', help = 'histogram plot', epilog = "eg: plotext hist --path test --xcolumn 1 --ycolumns 2 --lines 5000 --title 'Histogram Test'") hist.add_argument("-b", "--bins", nargs = 1, type = int, default = [10], help = "histogram bins (10 by default)") image = subparser.add_parser('image', parents = [path_parser, common_parser], description = 'plots an image from path', help = 'plots an image from file path', epilog = "eg: plotext image --path test") gif = subparser.add_parser('gif', parents = [path_parser, common_parser], description = 'plays a gif image from path', help = 'plays a gif image from path', epilog = "eg: plotext gif --path test") video = subparser.add_parser('video', parents = [path_parser, common_parser], description = 'plays a video from path', help = 'plays a video from path', epilog = "eg: plotext video --path test --from_youtube True") video.add_argument("-fy", "--from_youtube", nargs = 1, type = str, default = ["False"], choices = ["True", "False"], metavar = "BOOL", help = "set it to True to render the colors correctly for videos downloaded from youtube; default is False") youtube = subparser.add_parser('youtube', description = 'plays a youtube video from url', help = 'plays a youtube video from url', epilog = "eg: plotext youtube --url test") youtube.add_argument("-u", "--url", action = 'store', dest = 'url', nargs = 1, type = str, metavar = "URL", help = "the url of a youtube video; use 'test' for a test video") return parser def main(argv = None): parser = build_parser() args = parser.parse_args(argv) type = args.type first_row = 0 if type != "youtube": path = args.path first_row = args.first_row clt = True if args.clear_terminal[-1] == 'True' else False sleep = args.sleep[0] def get_xY(data): l = len(data) xcol = args.xcolumn[0] ycols = args.ycolumns xcol = "none" if xcol == "none" else int(xcol) if int(xcol) - 1 in range(l) else "none" all_ycols = [el for el in range(l) if el != xcol] ycols = all_ycols if ycols == ["all"] else [int(el) for el in ycols if int(el) - 1 in range(l)] x = list(range(1, len(data[0]) + 1)) if xcol == "none" else data[xcol - 1] Y = [data[i - 1] for i in ycols] return x, Y def plot(x, Y): for y in Y: plt.plot(x, y, marker = marker, color = color) if type in ['plot', 'plotter'] else None plt.scatter(x, y, marker = marker, color = None if type == 'plotter' else color) if type in ['scatter', 'plotter'] else None plt.bar(x, y, marker = marker, color = color, width = width, orientation = orientation, fill = fill) if type == 'bar' else None plt.hist(y, marker = marker, color = color, orientation = orientation, fill = fill, bins = bins) if type == 'hist' else None plt.clt() if clt else None plt.show() plt.cld() plt.sleep(sleep) data_plot = ['scatter', 'plot', 'plotter', 'bar', 'hist'] test_path = 'test' if type in data_plot: lines = args.lines[0] delimiter = args.delimiter[0] title = args.title[0] xlabel = args.xlabel[0] ylabel = args.ylabel[0] grid = True if args.grid[-1] == 'True' else False orientation = args.orientation[0] if type in ['bar', 'hist'] else 'v' fill = args.fill[0] == 'True' if type in ['bar', 'hist'] else False width = args.width[0] if type == 'bar' else 0 bins = args.bins[0] if type in 'hist' else None marker = args.marker[0] color = args.color[0] plt.title(title); plt.xlabel(xlabel) plt.ylabel(ylabel) plt.grid(grid) if path == test_path: plt.plotsize(None, plt.th() - 3) if type != "bar": plt.download(plt.test_data_url, test_path, log = True) else: plt.download(plt.test_bar_data_url, test_path, log = True) path = test_path if path is None: def plot_text(text): data = plt._utility.read_lines(text, delimiter = delimiter) data = plt.transpose(data) x, Y = get_xY(data) plot(x, Y) for _ in range(first_row): sys.stdin.readline() text = [] i = 0 for line in iter(sys.stdin.readline, ''): text.append(line) i+=1; if len(text) == lines: plot_text(text) text = [] if len(text) > 0: # this is when there is some residual data to be plotted, not lines long plot_text(text) text = [] else: data = plt.read_data(path, delimiter = delimiter, first_row = first_row) data = plt.transpose(data) x, Y = get_xY(data) chunks = len(x) // lines + (1 if len(x) % lines else 0) for c in range(chunks): xc = x[c * lines: (c + 1) * lines] Yc = [y[c * lines: (c + 1) * lines] for y in Y] plot(xc, Yc) elif type == 'image': if path == test_path: plt.plotsize(None, plt.th() - 3) plt.download(plt.test_image_url, test_path, log = True) path = test_path plt.image_plot(path, fast = True) plt.clt() if clt else None plt.show() elif type == 'gif': if path == test_path: plt.plotsize(None, plt.th() - 3) plt.download(plt.test_gif_url, test_path, log = True) path = test_path plt.play_gif(path) elif type == 'video': if path == test_path: plt.plotsize(None, plt.th() - 3) plt.download(plt.test_video_url, test_path, log = True) path = test_path from_youtube = True if args.from_youtube[-1] == 'True' else False plt.play_video(path, from_youtube) elif type == 'youtube': url = args.url[-1] url = plt.test_youtube_url if url == test_path else url plt.play_youtube(url) if os.path.isfile(test_path): plt.delete_file(test_path, True) if __name__ == "__main__": sys.exit(main())