import sys, shutil, os, re, math, inspect from plotext._dict import * ############################################### ######### Number Manipulation ########## ############################################### def round(n, d = 0): # the standard round(0.5) = 0 instead of 1; this version rounds 0.5 to 1 n *= 10 ** d f = math.floor(n) r = f if n - f < 0.5 else math.ceil(n) return r * 10 ** (-d) def mean(x, y, p = 1): # mean of x and y with optional power p; if p tends to 0 the minimum is returned; if p tends to infinity the max is returned; p = 1 is the standard mean return ((x ** p + y ** p) / 2) ** (1 / p) def replace(data, data2, element = None): # replace element in data with correspondent in data2 when element is found res = [] for i in range(len(data)): el = data[i] if data[i] != element else data2[i] res.append(el) return res def try_float(data): # it turn a string into float if it can try: return float(data) except: return data def quantile(data, q): # calculate the quantile of a given array data = sorted(data) index = q * (len(data) - 1) if index.is_integer(): return data[int(index)] else: return (data[int(index)] + data[int(index) + 1]) / 2 ############################################### ########### List Creation ############## ############################################### def linspace(lower, upper, length = 10): # it returns a lists of numbers from lower to upper with given length slope = (upper - lower) / (length - 1) if length > 1 else 0 return [lower + x * slope for x in range(length)] def sin(periods = 2, length = 200, amplitude = 1, phase = 0, decay = 0): # sinusoidal data with given parameters f = 2 * math.pi * periods / (length - 1) phase = math.pi * phase d = decay / length return [amplitude * math.sin(f * el + phase) * math.exp(- d * el) for el in range(length)] def square(periods = 2, length = 200, amplitude = 1): T = length / periods step = lambda t: amplitude if t % T <= T / 2 else - amplitude return [step(i) for i in range(length)] def to_list(data, length): # eg: to_list(1, 3) = [1, 1 ,1]; to_list([1,2,3], 6) = [1, 2, 3, 1, 2, 3] data = data if isinstance(data, list) else [data] * length data = data * math.ceil(length / len(data)) if len(data) > 0 else [] return data[ : length] def difference(data1, data2) : # elements in data1 not in date2 return [el for el in data1 if el not in data2] ############################################### ######### List Transformation ########## ############################################### def log(data): # it apply log function to the data return [math.log10(el) for el in data] if isinstance(data, list) else math.log10(data) def power10(data): # it apply log function to the data return [10 ** el for el in data] def floor(data): # it floors a list of data return list(map(math.floor, data)) def repeat(data, length): # repeat the same data till length is reached l = len(data) if type(data) == list else 1 data = join([data] * math.ceil(length / l)) return data[ : length] ############################################### ########## List Manipulation ########### ############################################### def no_duplicates(data): # removes duplicates from a list return list(set(list(data))) #return list(dict.fromkeys(data)) # it takes double time def join(data): # flatten lists at first level #return [el for row in data for el in row] return [el for row in data for el in (join(row) if type (row) == list else [row])] def cumsum(data): # it returns the cumulative sums of a list; eg: cumsum([0,1,2,3,4]) = [0,1,3,6,10] s = [0] for i in range(len(data)): s.append(s[-1] + data[i]) return s[1:] ############################################### ######### Matrix Manipulation ########## ############################################### def matrix_size(matrix): # cols, height return [len(matrix[0]), len(matrix)] if matrix != [] else [0, 0] def transpose(data, length = 1): # it needs no explanation return [[]] * length if data == [] else list(map(list, zip(*data))) def vstack(matrix, extra): # vertical stack of two matrices return extra + matrix # + extra def hstack(matrix, extra): # horizontal stack of two matrices lm, le = len(matrix), len(extra) l = max(lm, le) return [matrix[i] + extra[i] for i in range(l)] def turn_gray(matrix): # it takes a standard matrix and turns it into an grayscale one M, m = max(join(matrix), default = 0), min(join(matrix), default = 0) to_gray = lambda el: tuple([int(255 * (el - m) / (M - m))] * 3) if M != m else (127, 127, 127) return [[to_gray(el) for el in l] for l in matrix] def brush(*lists): # remove duplicates from lists x, y, z ... l = min(map(len, lists)) lists = [el[:l] for el in lists] z = list(zip(*lists)) z = no_duplicates(z) #z = sorted(z)#, key = lambda x: x[0]) lists = transpose(z, len(lists)) return lists ############################################### ######### String Manipulation ########### ############################################### nl = "\n" def only_spaces(string): # it returns True if string is made of only empty spaces or is None or '' return (type(string) == str) and (string == len(string) * space) #and len(string) != 0 def format_time(time): # it properly formats the computational time t = time if time is not None else 0 unit = 's' if t >= 1 else 'ms' if t >= 10 ** -3 else 'µs' p = 0 if unit == 's' else 3 if unit == 'ms' else 6 t = round(10 ** p * t, 1) l = len(str(int(t))) t = str(t) #t = ' ' * (3 - l) + t return t[ : l + 2] + ' ' + unit positive_color = 'green+' negative_color = 'red' title_color = 'cyan+' def format_strings(string1, string2, color = positive_color): # returns string1 in bold and with color + string2 with a pre-formatted style return colorize(string1, color, "bold") + " " + colorize(string2, style = info_style) def correct_coord(string, label, coord): # In the attempt to insert a label in string at given coordinate, the coordinate is adjusted so not to hit the borders of the string l = len(label) b, e = max(coord - l + 1, 0), min(coord + l, len(string) - 1) data = [i for i in range(b, e) if string[i] is space] b, e = min(data, default = coord - l + 1), max(data, default = coord + l) b, e = e - l + 1, b + l return (b + e - l) // 2 def no_char_duplicates(string, char): # it remove char duplicates from string pattern = char + '{2,}' string = re.sub(pattern, char, string) return string def read_lines(text, delimiter = None, columns = None): # from a long text to well formatted data delimiter = " " if delimiter is None else delimiter data = [] columns = len(no_char_duplicates(text[0], delimiter).split(delimiter)) if columns is None else columns for i in range(len(text)): row = text[i] row = no_char_duplicates(row, delimiter) row = row.split(delimiter) row = [el.replace('\n', '') for el in row] cols = len(row) row = [row[col].replace('\n', '') if col in range(cols) else '' for col in range(columns)] row = [try_float(el) for el in row] data.append(row) return data def pad_string(num, length): # pad a number with spaces before to reach length num = str(num) l = len(num) return num + ' ' * (length - l) def max_length(strings): strings = map(str, strings) return max(map(len, strings), default = 0) ############################################### ########## File Manipulation ############ ############################################### def correct_path(path): folder, base = os.path.dirname(path), os.path.basename(path) folder = os.path.expanduser("~") if folder in ['', '~'] else folder path = os.path.join(folder, base) return path def is_file(path, log = True): # returns True if path exists res = os.path.isfile(path) print(format_strings("not a file:", path, negative_color)) if not res and log else None return res def script_folder(): # the folder of the script executed return parent_folder(inspect.getfile(sys._getframe(1))) def parent_folder(path, level = 1): # it return the parent folder of the path or file given; if level is higher then 1 the process is iterated if level <= 0: return path elif level == 1: return os.path.abspath(os.path.join(path, os.pardir)) else: return parent_folder(parent_folder(path, level - 1)) def join_paths(*args): # it join a list of string in a proper file path; if the first argument is ~ it is turnded into the used home folder path args = list(args) args[0] = _correct_path(args[0]) if args[0] == "~" else args[0] return os.path.abspath(os.path.join(*args)) def delete_file(path, log = True): # remove the file if it exists path = correct_path(path) if is_file(path): os.remove(path) print(format_strings("file removed:", path, negative_color)) if log else None def read_data(path, delimiter = None, columns = None, first_row = None, log = True): # it turns a text file into data lists path = correct_path(path) first_row = 0 if first_row is None else int(first_row) file = open(path, "r") text = file.readlines()[first_row:] file.close() print(format_strings("data read from", path)) if log else None return read_lines(text, delimiter, columns) def write_data(data, path, delimiter = None, columns = None, log = True): # it turns a matrix into a text file delimiter = " " if delimiter is None else delimiter cols = len(data[0]) cols = range(1, cols + 1) if columns is None else columns text = "" for row in data: row = [row[i - 1] for i in cols] row = list(map(str, row)) text += delimiter.join(row) + '\n' save_text(text, path, log = log) def save_text(text, path, append = False, log = True): # it saves some text to the path selected path = correct_path(path) mode = "a" if append else "w+" with open(path , mode, encoding='utf-8') as file: file.write(text) print(format_strings("text saved in", path)) if log else None def download(url, path, log = True): # it download the url (image, video, gif etc) to path from urllib.request import urlretrieve path = correct_path(path) urlretrieve(url, path) print(format_strings('url saved in', path)) if log else None ############################################### ######### Platform Utilities ############ ############################################### def is_ipython(): # true if running in ipython shenn try: __IPYTHON__ return True except NameError: return False def platform(): # the platform (unix or windows) you are using plotext in platform = sys.platform if platform in {'win32', 'cygwin'}: return 'windows' else: return 'unix' platform = platform() # to enable ascii escape color sequences if platform == "windows": import subprocess subprocess.call('', shell = True) def terminal_size(): # it returns the terminal size as [width, height] try: size = shutil.get_terminal_size() return list(size) except OSError: return [None, None] terminal_width = lambda: terminal_size()[0] tw = terminal_width terminal_height = lambda: terminal_size()[1] th = terminal_height def clear_terminal(lines = None): # it cleat the entire terminal, or the specified number of lines if lines is None: write('\033c') else: for r in range(lines): write("\033[A") # moves the curson up write("\033[2K") # clear the entire line def write(string): # the print function used by plotext sys.stdout.write(string) class memorize: # it memorise the arguments of a function, when used as its decorator, to reduce computational time def __init__(self, f): self.f = f self.memo = {} def __call__(self, *args): if not args in self.memo: self.memo[args] = self.f(*args) return self.memo[args] ############################################## ######### Marker Utilities ########### ############################################## space = ' ' # the default null character that appears as background to all plots plot_marker = "hd" if platform == 'unix' else 'dot' hd_markers = {hd_codes[el] : el for el in hd_codes} fhd_markers = {fhd_codes[el] : el for el in fhd_codes} braille_markers = {braille_codes[el] : el for el in braille_codes} simple_bar_marker = '▇' @memorize def get_hd_marker(code): return hd_codes[code] if len(code) == 4 else fhd_codes[code] if len(code) == 6 else braille_codes[code] def marker_factor(marker, hd, fhd, braille): # useful to improve the resolution of the canvas for higher resolution markers return hd if marker == 'hd' else fhd if marker == 'fhd' else braille if marker == 'braille' else 1 ############################################## ########### Color Utilities ############ ############################################## # A user could specify three types of colors # an integer for 256 color codes # a tuple for RGB color codes # a string for 16 color codes or styles # Along side the user needs to specify whatever it is for background / fullground / style # which plotext calls 'character' = 0 / 1 / 2 #colors_no_plus = [el for el in colors if '+' not in el and el + '+' not in colors and el is not no_color] # basically just [black, white] def get_color_code(color): # the color number code from color string color = color.strip() return color_codes[color] def get_color_name(code): # the color string from color number code codes = list(color_codes.values()) return colors[codes.index(code)] if code in codes else no_color def is_string_color(color): return isinstance(color, str) and color.strip() in colors def is_integer_color(color): return isinstance(color, int) and 0 <= color <= 255 def is_rgb_color(color): is_rgb = isinstance(color, list) or isinstance(color, tuple) is_rgb = is_rgb and len(color) == 3 is_rgb = is_rgb and all([is_integer_color(el) for el in color]) return is_rgb def is_color(color): return is_string_color(color) or is_integer_color(color) or is_rgb_color(color) def colorize(string, color = None, style = None, background = None, show = False): # it paints a text with given fullground and background color string = apply_ansi(string, background, 0) string = apply_ansi(string, color, 1) string = apply_ansi(string, style, 2) if show: print(string) return string def uncolorize(string): # remove color codes from colored string colored = lambda: ansi_begin in string while colored(): b = string.index(ansi_begin) e = string[b : ].index('m') + b + 1 string = string.replace(string[b : e], '') return string def apply_ansi(string, color, character): begin, end = ansi(color, character) return begin + string + end #ansi_begin = '\033[' ansi_begin = '\x1b[' ansi_end = ansi_begin + '0m' @memorize def colors_to_ansi(fullground, style, background): color = [background, fullground, style] return ''.join([ansi(color[i], i)[0] for i in range(3)]) @memorize def ansi(color, character): if color == no_color: return ['', ''] col, fg, tp = '', '', '' if character == 2 and is_style(color): col = get_style_codes(color) col = ';'.join([str(el) for el in col]) elif character != 2: fg = '38;' if character == 1 else '48;' tp = '5;' if is_string_color(color): col = str(get_color_code(color)) elif is_integer_color(color): col = str(color) elif is_rgb_color(color): col = ';'.join([str(el) for el in color]) tp = '2;' is_color = col != '' begin = ansi_begin + fg + tp + col + 'm' if is_color else '' end = ansi_end if is_color else '' return [begin, end] ## This section is useful to produce html colored version of the plot and to translate all color types (types 0 and 1) in rgb (type 2 in plotext) and avoid confusion. the match is almost exact and it depends on the terminal i suppose def to_rgb(color): if is_string_color(color): # from 0 to 1 color = get_color_code(color) #color = type0_to_type1_codes[code] if is_integer_color(color): # from 0 or 1 to 2 return type1_to_type2_codes[color] return color ############################################## ############ Style Codes ############## ############################################## no_style = 'default' styles = list(style_codes.keys()) + [no_style] info_style = 'dim' def get_style_code(style): # from single style to style number code style = style.strip() return style_codes[style] def get_style_codes(style): # from many styles (separated by space) to as many number codes style = style.strip().split() codes = [get_style_code(el) for el in style if el in styles] codes = no_duplicates(codes) return codes def get_style_name(code): # from style number code to style name codes = list(style_codes.values()) return styles[codes.index(code)] if code in codes else no_style def clean_styles(style): # it returns a well written sequence of styles (separated by spaces) from a possible confused one codes = get_style_codes(style) return ' '.join([get_style_name(el) for el in codes]) def is_style(style): style = style.strip().split() if isinstance(style, str) else [''] return any([el in styles for el in style]) ############################################## ########### Plot Utilities ############ ############################################## def set_data(x = None, y = None): # it return properly formatted x and y data lists if x is None and y is None : x, y = [], [] elif x is not None and y is None: y = x x = list(range(1, len(y) + 1)) lx, ly = len(x), len(y) if lx != ly: l = min(lx, ly) x = x[ : l] y = y[ : l] return [list(x), list(y)] ############################################## ####### Figure Class Utilities ######## ############################################## def set_sizes(sizes, size_max): # given certain widths (or heights) - some of them are None - it sets them so to respect max value bins = len(sizes) for s in range(bins): size_set = sum([el for el in sizes[0 : s] + sizes[s + 1 : ] if el is not None]) available = max(size_max - size_set, 0) to_set = len([el for el in sizes[s : ] if el is None]) sizes[s] = available // to_set if sizes[s] is None else sizes[s] return sizes def fit_sizes(sizes, size_max): # honestly forgot the point of this function: yeeeeei :-) but it is useful - probably assumes all sizes not None (due to set_sizes) and reduces those that exceed size_max from last one to first bins = len(sizes) s = bins - 1 #while (sum(sizes) != size_max if not_less else sum(sizes) > size_max) and s >= 0: while sum(sizes) > size_max and s >= 0: other_sizes = sum([sizes[i] for i in range(bins) if i != s]) sizes[s] = max(size_max - other_sizes, 0) s -= 1 return sizes ############################################## ####### Build Class Utilities ######### ############################################## def get_first(data, test = True): # if test take the first element, otherwise the second return data[0] if test else data[1] def apply_scale(data, test = False): # apply log scale if test return log(data) if test else data def reverse_scale(data, test = False): # apply log scale if test return power10(data) if test else data def replace_none(data, num_data): # replace None elements in data with correspondent in num_data return [data[i] if data[i] is not None else num_data[i] for i in range(len(data))] numerical = lambda el: not (el is None or math.isnan(el)) or isinstance(el, str) # in the case of string datetimes all_numerical = lambda data: all([numerical(el) for el in data]) def get_lim(data): # it returns the data minimum and maximum limits data = [el for el in data if numerical(el)] m = min(data, default = 0) M = max(data, default = 0) m, M = (m, M) if m != M else (0.5 * m, 1.5 * m) if m == M != 0 else (-1, 1) return [m, M] def get_matrix_data(data, lim, bins): # from data to relative canvas coordinates change = lambda el: 0.5 + (bins - 1) * (el - lim[0]) / (lim[1] - lim[0]) # round is so that for example 9.9999 = 10, otherwise the floor function will give different results return [math.floor(round(change(el), 8)) if numerical(el) else el for el in data] def get_lines(x, y, *other): # it returns the lines between all couples of data points like x[i], y[i] to x[i + 1], y[i + 1]; other are the lisXt of markers and colors that needs to be elongated # if len(x) * len(y) == 0: # return [], [], *[[]] * len(other) o = transpose(other, len(other)) xl, yl, ol = [[] for i in range(3)] for n in range(len(x) - 1): xn, yn = x[n : n + 2], y[n : n + 2] xn, yn = get_line(xn, yn) xl += xn[:-1] yl += yn[:-1] ol += [o[n]] * len(xn[:-1]) xl = xl + [x[-1]] if x != [] else xl yl = yl + [y[-1]] if x != [] else yl ol = ol + [o[-1]] if x != [] else ol return [xl, yl] + transpose(ol, len(other)) def get_line(x, y): # it returns a line of points from x[0],y[0] to x[1],y[1] distanced between each other in x and y by at least 1. if not all_numerical(join([x, y])): return x, y x0, x1 = x y0, y1 = y dx, dy = int(x1) - int(x0), int(y1) - int(y0) ax, ay = abs(dx), abs(dy) a = int(max(ax, ay) + 1) x = [int(el) for el in linspace(x0, x1, a)] y = [int(el) for el in linspace(y0, y1, a)] return [x, y] def get_fill_level(fill, lim, bins): if fill is False: return False elif isinstance(fill, str): return fill else: fill = min(max(fill, lim[0]), lim[1]) fill = get_matrix_data([fill], lim, bins)[0] return fill def find_filling_values(x, y, y0): xn, yn, yf = [[]] * 3 l = len(x); while len(x) > 0: i = len(xn) xn.append(x[i]) yn.append(y[i]) J = [j for j in range(l) if x[j] == x[i]] if J != []: Y = [y[j] for j in J] j = Y.index(min(Y)) J.pop(j) [x.pop(j) for j in J] [y.pop(j) for j in J] yf.append(y[j]) return xn, yn, yf def get_fill_boundaries(x, y): xm = [] l = len(x) for i in range(l): xi, yi = x[i], y[i] I = [j for j in range(l) if x[j] == xi and y[j] < yi] Y = [y[j] for j in I] m = min(Y, default = yi) xm.append([x[i], m]) x, m = transpose(xm) return m def fill_data(x, y, y0, *other): # it fills x, y with y data points reaching y0; and c are the list of markers and colors that needs to be elongated #y0 = get_fill_boundaries(x, y) y0 = get_fill_boundaries(x, y) if isinstance(y0, str) else [y0] * len(x) o = transpose(other, len(other)) xf, yf, of = [[] for i in range(3)] xy = [] for i in range(len(x)): xi, yi, y0i = x[i], y[i], y0[i] if [xi, yi] not in xy: xy.append([xi, yi]) yn = range(y0i, yi + 1) if y0i < yi else range(yi, y0i) if y0i > yi else [y0i] yn = list(yn) xn = [xi] * len(yn) xf += xn yf += yn of += [o[i]] * len(xn) return [xf, yf] + transpose(of, len(other)) def remove_outsiders(x, y, width, height, *other): I = [i for i in range(len(x)) if x[i] in range(width) and y[i] in range(height)] o = transpose(other, len(other)) return transpose([(x[i], y[i], *o[i]) for i in I], 2 + len(other)) def get_labels(ticks): # it returns the approximated string version of the data ticks d = distinguishing_digit(ticks) formatting_string = "{:." + str(d + 1) + "f}" labels = [formatting_string.format(el) for el in ticks] pos = [el.index('.') + d + 2 for el in labels] labels = [labels[i][: pos[i]] for i in range(len(labels))] all_integers = all(map(lambda el: el == int(el), ticks)) labels = [add_extra_zeros(el, d) if len(labels) > 1 else el for el in labels] if not all_integers else [str(int(el)) for el in ticks] #sign = any([el < 0 for el in ticks]) #labels = ['+' + labels[i] if ticks[i] > 0 and sign else labels[i] for i in range(len(labels))] return labels def distinguishing_digit(data): # it return the minimum amount of decimal digits necessary to distinguish all elements of a list #data = [el for el in data if 'e' not in str(el)] d = [_distinguishing_digit(data[i], data[i + 1]) for i in range(len(data) - 1)] return max(d, default = 1) def _distinguishing_digit(a, b): # it return the minimum amount of decimal digits necessary to distinguish a from b (when both are rounded to those digits). d = abs(a - b) d = 0 if d == 0 else - math.log10(2 * d) #d = round(d, 10) d = 0 if d < 0 else math.ceil(d) d = d + 1 if round(a, d) == round(b, d) else d return d def add_extra_zeros(label, d): # it adds 0s at the end of a label if necessary zeros = len(label) - 1 - label.index('.' if 'e' not in label else 'e') if zeros < d: label += '0' * (d - zeros) return label def add_extra_spaces(labels, side): # it adds empty spaces before or after the labels if necessary length = 0 if labels == [] else max_length(labels) if side == "left": labels = [space * (length - len(el)) + el for el in labels] if side == "right": labels = [el + space * (length - len(el)) for el in labels] return labels def hd_group(x, y, xf, yf): # it returns the real coordinates of the HD markers and the matrix that defines the marker l, xfm, yfm = len(x), max(xf), max(yf) xm = [el // xfm if numerical(el) else el for el in x] ym = [el // yfm if numerical(el) else el for el in y] m = {} for i in range(l): xyi = xm[i], ym[i] xfi, yfi = xf[i], yf[i] mi = [[0 for x in range(xfi)] for y in range(yfi)] m[xyi] = mi for i in range(l): xyi = xm[i], ym[i] if all_numerical(xyi): xk, yk = x[i] % xfi, y[i] % yfi xk, yk = math.floor(xk), math.floor(yk) m[xyi][yk][xk] = 1 x, y = transpose(m.keys(), 2) m = [tuple(join(el[::-1])) for el in m.values()] return x, y, m ############################################### ############# Bar Functions ############## ############################################### def bars(x, y, width, minimum): # given the bars center coordinates and height, it returns the full bar coordinates # if x == []: # return [], [] bins = len(x) #bin_size_half = (max(x) - min(x)) / (bins - 1) * width / 2 bin_size_half = width / 2 # adjust the bar width according to the number of bins if bins > 1: bin_size_half *= (max(x) - min(x)) / (bins - 1) xbar, ybar = [], [] for i in range(bins): xbar.append([x[i] - bin_size_half, x[i] + bin_size_half]) ybar.append([minimum, y[i]]) return xbar, ybar def set_multiple_bar_data(*args): l = len(args) Y = [] if l == 0 else args[0] if l == 1 else args[1] Y = [Y] if not isinstance(Y, list) or len(Y) == 0 else Y m = len(Y[0]) x = [] if l == 0 else list(range(1, m + 1)) if l == 1 else args[0] return x, Y def hist_data(data, bins = 10, norm = False): # it returns data in histogram form if norm is False. Otherwise, it returns data in density form where all bins sum to 1. #data = [round(el, 15) for el in data] # if data == []: # return [], [] bins = 0 if len(data) == 0 else bins m, M = min(data, default = 0), max(data, default = 0) data = [(el - m) / (M - m) * bins if el != M else bins - 1 for el in data] data = [int(el) for el in data] histx = linspace(m, M, bins) histy = [0] * bins for el in data: histy[el] += 1 if norm: histy = [el / len(data) for el in histy] return histx, histy def single_bar(x, y, ylabel, marker, colors): l = len(y) lc = len(colors) xs = colorize(str(x), 'gray+', 'bold') bar = [marker * el for el in y] bar = [apply_ansi(bar[i], colors[i % lc], 1) for i in range(l)] ylabel = colorize(f'{ylabel:.2f}', 'gray+', 'bold') bar = xs + space + ''.join(bar) + space + ylabel return bar def bar_data(*args, width = None, mode = 'stacked'): x, Y = set_multiple_bar_data(*args) x = list(map(str, x)) x = add_extra_spaces(x, 'right') lx = len(x[0]) y = [sum(el) for el in transpose(Y)] if mode == 'stacked' else Y ly = max_length([round(el, 2) for el in join(y)]) width_term = terminal_width() width = width_term if width is None else min(width, width_term) width = max(width, lx + ly + 2 + 1) my = max(join(y)) my = 1 if my == 0 else my dx = my / (width - lx - ly - 2) Yi = [[round(el / dx, 0) for el in y] for y in Y] Yi = transpose(Yi) return x, y, Yi, width def correct_marker(marker = None): return simple_bar_marker if marker is None else marker[0] def get_title(title, width): out = '' if title is not None: l = len(uncolorize(title)) w1 = (width - 2 - l) // 2; w2 = width - l - 2 - w1 l1 = '─' * w1 + space l2 = space + '─' * w2 out = colorize(l1 + title + l2, 'gray+', 'bold') + '\n' return out def get_simple_labels(marker, labels, colors, width): out = '\n' if labels != None: l = len(labels) lc = len(colors) out = space.join([colorize(marker * 3, colors[i % lc]) + space + colorize(labels[i], 'gray+', 'bold') for i in range(l)]) out = '\n' + get_title(out, width) return out ############################################### ############# Box Functions ############## ############################################### def box(x, y, width, minimum): # given the bars center coordinates and height, it returns the full bar coordinates # if x == []: # return [], [] bins = len(x) #bin_size_half = (max(x) - min(x)) / (bins - 1) * width / 2 bin_size_half = width / 2 # adjust the bar width according to the number of bins if bins > 1: bin_size_half *= (max(x) - min(x)) / (bins - 1) c, q1, q2, q3, h, l = [], [], [], [], [], [] xbar, ybar, mybar = [], [], [] for i in range(bins): c.append(x[i]) xbar.append([x[i] - bin_size_half, x[i] + bin_size_half]) q1.append(quantile(y[i], 0.25)) q2.append(quantile(y[i], 0.50)) q3.append(quantile(y[i], 0.75)) h.append(max(y[i])) l.append(min(y[i])) return q1, q2, q3, h, l, c, xbar ############################################## ########## Image Utilities ############# ############################################## def update_size(size_old, size_new): # it resize an image to the desired size, maintaining or not its size ratio and adding or not a pixel averaging factor with resample = True size_old = [size_old[0], size_old[1] / 2] ratio_old = size_old[1] / size_old[0] size_new = replace(size_new, size_old) ratio_new = size_new[1] / size_new[0] #ratio_new = size_new[1] / size_new[0] size_new = [1 if el == 0 else el for el in size_new] return [int(size_new[0]), int(size_new[1])] def image_to_matrix(image): # from image to a matrix of pixels pixels = list(image.getdata()) width, height = image.size return [pixels[i * width:(i + 1) * width] for i in range(height)]