2025-06-07 20:06:19 +02:00

834 lines
31 KiB
Python

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)]