Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/plotext/_utility.py: 32%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

603 statements  

1import sys, shutil, os, re, math, inspect 

2from plotext._dict import * 

3 

4############################################### 

5######### Number Manipulation ########## 

6############################################### 

7 

8def round(n, d = 0): # the standard round(0.5) = 0 instead of 1; this version rounds 0.5 to 1 

9 n *= 10 ** d 

10 f = math.floor(n) 

11 r = f if n - f < 0.5 else math.ceil(n) 

12 return r * 10 ** (-d) 

13 

14def 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 

15 return ((x ** p + y ** p) / 2) ** (1 / p) 

16 

17def replace(data, data2, element = None): # replace element in data with correspondent in data2 when element is found 

18 res = [] 

19 for i in range(len(data)): 

20 el = data[i] if data[i] != element else data2[i] 

21 res.append(el) 

22 return res 

23 

24def try_float(data): # it turn a string into float if it can 

25 try: 

26 return float(data) 

27 except: 

28 return data 

29 

30def quantile(data, q): # calculate the quantile of a given array 

31 data = sorted(data) 

32 index = q * (len(data) - 1) 

33 if index.is_integer(): 

34 return data[int(index)] 

35 else: 

36 return (data[int(index)] + data[int(index) + 1]) / 2 

37 

38############################################### 

39########### List Creation ############## 

40############################################### 

41 

42def linspace(lower, upper, length = 10): # it returns a lists of numbers from lower to upper with given length 

43 slope = (upper - lower) / (length - 1) if length > 1 else 0 

44 return [lower + x * slope for x in range(length)] 

45 

46def sin(periods = 2, length = 200, amplitude = 1, phase = 0, decay = 0): # sinusoidal data with given parameters 

47 f = 2 * math.pi * periods / (length - 1) 

48 phase = math.pi * phase 

49 d = decay / length 

50 return [amplitude * math.sin(f * el + phase) * math.exp(- d * el) for el in range(length)] 

51 

52def square(periods = 2, length = 200, amplitude = 1): 

53 T = length / periods 

54 step = lambda t: amplitude if t % T <= T / 2 else - amplitude 

55 return [step(i) for i in range(length)] 

56 

57def to_list(data, length): # eg: to_list(1, 3) = [1, 1 ,1]; to_list([1,2,3], 6) = [1, 2, 3, 1, 2, 3] 

58 data = data if isinstance(data, list) else [data] * length 

59 data = data * math.ceil(length / len(data)) if len(data) > 0 else [] 

60 return data[ : length] 

61 

62def difference(data1, data2) : # elements in data1 not in date2 

63 return [el for el in data1 if el not in data2] 

64 

65############################################### 

66######### List Transformation ########## 

67############################################### 

68 

69def log(data): # it apply log function to the data 

70 return [math.log10(el) for el in data] if isinstance(data, list) else math.log10(data) 

71 

72def power10(data): # it apply log function to the data 

73 return [10 ** el for el in data] 

74 

75def floor(data): # it floors a list of data 

76 return list(map(math.floor, data)) 

77 

78def repeat(data, length): # repeat the same data till length is reached 

79 l = len(data) if type(data) == list else 1 

80 data = join([data] * math.ceil(length / l)) 

81 return data[ : length] 

82 

83############################################### 

84########## List Manipulation ########### 

85############################################### 

86 

87def no_duplicates(data): # removes duplicates from a list 

88 return list(set(list(data))) 

89 #return list(dict.fromkeys(data)) # it takes double time 

90 

91def join(data): # flatten lists at first level 

92 #return [el for row in data for el in row] 

93 return [el for row in data for el in (join(row) if type (row) == list else [row])] 

94 

95def cumsum(data): # it returns the cumulative sums of a list; eg: cumsum([0,1,2,3,4]) = [0,1,3,6,10] 

96 s = [0] 

97 for i in range(len(data)): 

98 s.append(s[-1] + data[i]) 

99 return s[1:] 

100 

101############################################### 

102######### Matrix Manipulation ########## 

103############################################### 

104 

105def matrix_size(matrix): # cols, height 

106 return [len(matrix[0]), len(matrix)] if matrix != [] else [0, 0] 

107 

108def transpose(data, length = 1): # it needs no explanation 

109 return [[]] * length if data == [] else list(map(list, zip(*data))) 

110 

111def vstack(matrix, extra): # vertical stack of two matrices 

112 return extra + matrix # + extra 

113 

114def hstack(matrix, extra): # horizontal stack of two matrices 

115 lm, le = len(matrix), len(extra) 

116 l = max(lm, le) 

117 return [matrix[i] + extra[i] for i in range(l)] 

118 

119def turn_gray(matrix): # it takes a standard matrix and turns it into an grayscale one 

120 M, m = max(join(matrix), default = 0), min(join(matrix), default = 0) 

121 to_gray = lambda el: tuple([int(255 * (el - m) / (M - m))] * 3) if M != m else (127, 127, 127) 

122 return [[to_gray(el) for el in l] for l in matrix] 

123 

124def brush(*lists): # remove duplicates from lists x, y, z ... 

125 l = min(map(len, lists)) 

126 lists = [el[:l] for el in lists] 

127 z = list(zip(*lists)) 

128 z = no_duplicates(z) 

129 #z = sorted(z)#, key = lambda x: x[0]) 

130 lists = transpose(z, len(lists)) 

131 return lists 

132 

133############################################### 

134######### String Manipulation ########### 

135############################################### 

136 

137nl = "\n" 

138 

139def only_spaces(string): # it returns True if string is made of only empty spaces or is None or '' 

140 return (type(string) == str) and (string == len(string) * space) #and len(string) != 0 

141 

142def format_time(time): # it properly formats the computational time 

143 t = time if time is not None else 0 

144 unit = 's' if t >= 1 else 'ms' if t >= 10 ** -3 else 'µs' 

145 p = 0 if unit == 's' else 3 if unit == 'ms' else 6 

146 t = round(10 ** p * t, 1) 

147 l = len(str(int(t))) 

148 t = str(t) 

149 #t = ' ' * (3 - l) + t 

150 return t[ : l + 2] + ' ' + unit 

151 

152positive_color = 'green+' 

153negative_color = 'red' 

154title_color = 'cyan+' 

155 

156def format_strings(string1, string2, color = positive_color): # returns string1 in bold and with color + string2 with a pre-formatted style 

157 return colorize(string1, color, "bold") + " " + colorize(string2, style = info_style) 

158 

159def 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 

160 l = len(label) 

161 b, e = max(coord - l + 1, 0), min(coord + l, len(string) - 1) 

162 data = [i for i in range(b, e) if string[i] is space] 

163 b, e = min(data, default = coord - l + 1), max(data, default = coord + l) 

164 b, e = e - l + 1, b + l 

165 return (b + e - l) // 2 

166 

167def no_char_duplicates(string, char): # it remove char duplicates from string 

168 pattern = char + '{2,}' 

169 string = re.sub(pattern, char, string) 

170 return string 

171 

172def read_lines(text, delimiter = None, columns = None): # from a long text to well formatted data 

173 delimiter = " " if delimiter is None else delimiter 

174 data = [] 

175 columns = len(no_char_duplicates(text[0], delimiter).split(delimiter)) if columns is None else columns 

176 for i in range(len(text)): 

177 row = text[i] 

178 row = no_char_duplicates(row, delimiter) 

179 row = row.split(delimiter) 

180 row = [el.replace('\n', '') for el in row] 

181 cols = len(row) 

182 row = [row[col].replace('\n', '') if col in range(cols) else '' for col in range(columns)] 

183 row = [try_float(el) for el in row] 

184 data.append(row) 

185 return data 

186 

187def pad_string(num, length): # pad a number with spaces before to reach length 

188 num = str(num) 

189 l = len(num) 

190 return num + ' ' * (length - l) 

191 

192def max_length(strings): 

193 strings = map(str, strings) 

194 return max(map(len, strings), default = 0) 

195 

196############################################### 

197########## File Manipulation ############ 

198############################################### 

199 

200def correct_path(path): 

201 folder, base = os.path.dirname(path), os.path.basename(path) 

202 folder = os.path.expanduser("~") if folder in ['', '~'] else folder 

203 path = os.path.join(folder, base) 

204 return path 

205 

206def is_file(path, log = True): # returns True if path exists 

207 res = os.path.isfile(path) 

208 print(format_strings("not a file:", path, negative_color)) if not res and log else None 

209 return res 

210 

211def script_folder(): # the folder of the script executed 

212 return parent_folder(inspect.getfile(sys._getframe(1))) 

213 

214def 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 

215 if level <= 0: 

216 return path 

217 elif level == 1: 

218 return os.path.abspath(os.path.join(path, os.pardir)) 

219 else: 

220 return parent_folder(parent_folder(path, level - 1)) 

221 

222def 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 

223 args = list(args) 

224 args[0] = _correct_path(args[0]) if args[0] == "~" else args[0] 

225 return os.path.abspath(os.path.join(*args)) 

226 

227def delete_file(path, log = True): # remove the file if it exists 

228 path = correct_path(path) 

229 if is_file(path): 

230 os.remove(path) 

231 print(format_strings("file removed:", path, negative_color)) if log else None 

232 

233def read_data(path, delimiter = None, columns = None, first_row = None, log = True): # it turns a text file into data lists 

234 path = correct_path(path) 

235 first_row = 0 if first_row is None else int(first_row) 

236 file = open(path, "r") 

237 text = file.readlines()[first_row:] 

238 file.close() 

239 print(format_strings("data read from", path)) if log else None 

240 return read_lines(text, delimiter, columns) 

241 

242def write_data(data, path, delimiter = None, columns = None, log = True): # it turns a matrix into a text file 

243 delimiter = " " if delimiter is None else delimiter 

244 cols = len(data[0]) 

245 cols = range(1, cols + 1) if columns is None else columns 

246 text = "" 

247 for row in data: 

248 row = [row[i - 1] for i in cols] 

249 row = list(map(str, row)) 

250 text += delimiter.join(row) + '\n' 

251 save_text(text, path, log = log) 

252 

253def save_text(text, path, append = False, log = True): # it saves some text to the path selected 

254 path = correct_path(path) 

255 mode = "a" if append else "w+" 

256 with open(path , mode, encoding='utf-8') as file: 

257 file.write(text) 

258 print(format_strings("text saved in", path)) if log else None 

259 

260def download(url, path, log = True): # it download the url (image, video, gif etc) to path 

261 from urllib.request import urlretrieve 

262 path = correct_path(path) 

263 urlretrieve(url, path) 

264 print(format_strings('url saved in', path)) if log else None 

265 

266############################################### 

267######### Platform Utilities ############ 

268############################################### 

269 

270def is_ipython(): # true if running in ipython shenn 

271 try: 

272 __IPYTHON__ 

273 return True 

274 except NameError: 

275 return False 

276 

277def platform(): # the platform (unix or windows) you are using plotext in 

278 platform = sys.platform 

279 if platform in {'win32', 'cygwin'}: 

280 return 'windows' 

281 else: 

282 return 'unix' 

283 

284platform = platform() 

285 

286# to enable ascii escape color sequences 

287if platform == "windows": 

288 import subprocess 

289 subprocess.call('', shell = True) 

290 

291def terminal_size(): # it returns the terminal size as [width, height] 

292 try: 

293 size = shutil.get_terminal_size() 

294 return list(size) 

295 except OSError: 

296 return [None, None] 

297 

298terminal_width = lambda: terminal_size()[0] 

299tw = terminal_width 

300 

301terminal_height = lambda: terminal_size()[1] 

302th = terminal_height 

303 

304def clear_terminal(lines = None): # it cleat the entire terminal, or the specified number of lines 

305 if lines is None: 

306 write('\033c') 

307 else: 

308 for r in range(lines): 

309 write("\033[A") # moves the curson up 

310 write("\033[2K") # clear the entire line 

311 

312def write(string): # the print function used by plotext 

313 sys.stdout.write(string) 

314 

315class memorize: # it memorise the arguments of a function, when used as its decorator, to reduce computational time 

316 def __init__(self, f): 

317 self.f = f 

318 self.memo = {} 

319 def __call__(self, *args): 

320 if not args in self.memo: 

321 self.memo[args] = self.f(*args) 

322 return self.memo[args] 

323 

324############################################## 

325######### Marker Utilities ########### 

326############################################## 

327 

328space = ' ' # the default null character that appears as background to all plots 

329plot_marker = "hd" if platform == 'unix' else 'dot' 

330 

331hd_markers = {hd_codes[el] : el for el in hd_codes} 

332fhd_markers = {fhd_codes[el] : el for el in fhd_codes} 

333braille_markers = {braille_codes[el] : el for el in braille_codes} 

334simple_bar_marker = '▇' 

335 

336@memorize 

337def get_hd_marker(code): 

338 return hd_codes[code] if len(code) == 4 else fhd_codes[code] if len(code) == 6 else braille_codes[code] 

339 

340def marker_factor(marker, hd, fhd, braille): # useful to improve the resolution of the canvas for higher resolution markers 

341 return hd if marker == 'hd' else fhd if marker == 'fhd' else braille if marker == 'braille' else 1 

342 

343############################################## 

344########### Color Utilities ############ 

345############################################## 

346 

347# A user could specify three types of colors 

348 # an integer for 256 color codes 

349 # a tuple for RGB color codes 

350 # a string for 16 color codes or styles 

351 

352# Along side the user needs to specify whatever it is for background / fullground / style 

353# which plotext calls 'character' = 0 / 1 / 2 

354 

355 

356#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] 

357 

358def get_color_code(color): # the color number code from color string 

359 color = color.strip() 

360 return color_codes[color] 

361 

362def get_color_name(code): # the color string from color number code 

363 codes = list(color_codes.values()) 

364 return colors[codes.index(code)] if code in codes else no_color 

365 

366def is_string_color(color): 

367 return isinstance(color, str) and color.strip() in colors 

368 

369def is_integer_color(color): 

370 return isinstance(color, int) and 0 <= color <= 255 

371 

372def is_rgb_color(color): 

373 is_rgb = isinstance(color, list) or isinstance(color, tuple) 

374 is_rgb = is_rgb and len(color) == 3 

375 is_rgb = is_rgb and all([is_integer_color(el) for el in color]) 

376 return is_rgb 

377 

378def is_color(color): 

379 return is_string_color(color) or is_integer_color(color) or is_rgb_color(color) 

380 

381def colorize(string, color = None, style = None, background = None, show = False): # it paints a text with given fullground and background color 

382 string = apply_ansi(string, background, 0) 

383 string = apply_ansi(string, color, 1) 

384 string = apply_ansi(string, style, 2) 

385 if show: 

386 print(string) 

387 return string 

388 

389def uncolorize(string): # remove color codes from colored string 

390 colored = lambda: ansi_begin in string 

391 while colored(): 

392 b = string.index(ansi_begin) 

393 e = string[b : ].index('m') + b + 1 

394 string = string.replace(string[b : e], '') 

395 return string 

396 

397def apply_ansi(string, color, character): 

398 begin, end = ansi(color, character) 

399 return begin + string + end 

400 

401#ansi_begin = '\033[' 

402ansi_begin = '\x1b[' 

403ansi_end = ansi_begin + '0m' 

404 

405@memorize 

406def colors_to_ansi(fullground, style, background): 

407 color = [background, fullground, style] 

408 return ''.join([ansi(color[i], i)[0] for i in range(3)]) 

409 

410@memorize 

411def ansi(color, character): 

412 if color == no_color: 

413 return ['', ''] 

414 col, fg, tp = '', '', '' 

415 if character == 2 and is_style(color): 

416 col = get_style_codes(color) 

417 col = ';'.join([str(el) for el in col]) 

418 elif character != 2: 

419 fg = '38;' if character == 1 else '48;' 

420 tp = '5;' 

421 if is_string_color(color): 

422 col = str(get_color_code(color)) 

423 elif is_integer_color(color): 

424 col = str(color) 

425 elif is_rgb_color(color): 

426 col = ';'.join([str(el) for el in color]) 

427 tp = '2;' 

428 is_color = col != '' 

429 begin = ansi_begin + fg + tp + col + 'm' if is_color else '' 

430 end = ansi_end if is_color else '' 

431 return [begin, end] 

432 

433## 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 

434 

435def to_rgb(color): 

436 if is_string_color(color): # from 0 to 1 

437 color = get_color_code(color) 

438 #color = type0_to_type1_codes[code] 

439 if is_integer_color(color): # from 0 or 1 to 2 

440 return type1_to_type2_codes[color] 

441 return color 

442 

443############################################## 

444############ Style Codes ############## 

445############################################## 

446 

447no_style = 'default' 

448 

449styles = list(style_codes.keys()) + [no_style] 

450 

451info_style = 'dim' 

452 

453def get_style_code(style): # from single style to style number code 

454 style = style.strip() 

455 return style_codes[style] 

456 

457def get_style_codes(style): # from many styles (separated by space) to as many number codes 

458 style = style.strip().split() 

459 codes = [get_style_code(el) for el in style if el in styles] 

460 codes = no_duplicates(codes) 

461 return codes 

462 

463def get_style_name(code): # from style number code to style name 

464 codes = list(style_codes.values()) 

465 return styles[codes.index(code)] if code in codes else no_style 

466 

467def clean_styles(style): # it returns a well written sequence of styles (separated by spaces) from a possible confused one 

468 codes = get_style_codes(style) 

469 return ' '.join([get_style_name(el) for el in codes]) 

470 

471def is_style(style): 

472 style = style.strip().split() if isinstance(style, str) else [''] 

473 return any([el in styles for el in style]) 

474 

475############################################## 

476########### Plot Utilities ############ 

477############################################## 

478 

479def set_data(x = None, y = None): # it return properly formatted x and y data lists 

480 if x is None and y is None : 

481 x, y = [], [] 

482 elif x is not None and y is None: 

483 y = x 

484 x = list(range(1, len(y) + 1)) 

485 lx, ly = len(x), len(y) 

486 if lx != ly: 

487 l = min(lx, ly) 

488 x = x[ : l] 

489 y = y[ : l] 

490 return [list(x), list(y)] 

491 

492############################################## 

493####### Figure Class Utilities ######## 

494############################################## 

495 

496def set_sizes(sizes, size_max): # given certain widths (or heights) - some of them are None - it sets them so to respect max value 

497 bins = len(sizes) 

498 for s in range(bins): 

499 size_set = sum([el for el in sizes[0 : s] + sizes[s + 1 : ] if el is not None]) 

500 available = max(size_max - size_set, 0) 

501 to_set = len([el for el in sizes[s : ] if el is None]) 

502 sizes[s] = available // to_set if sizes[s] is None else sizes[s] 

503 return sizes 

504 

505def 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 

506 bins = len(sizes) 

507 s = bins - 1 

508 #while (sum(sizes) != size_max if not_less else sum(sizes) > size_max) and s >= 0: 

509 while sum(sizes) > size_max and s >= 0: 

510 other_sizes = sum([sizes[i] for i in range(bins) if i != s]) 

511 sizes[s] = max(size_max - other_sizes, 0) 

512 s -= 1 

513 return sizes 

514 

515############################################## 

516####### Build Class Utilities ######### 

517############################################## 

518 

519def get_first(data, test = True): # if test take the first element, otherwise the second 

520 return data[0] if test else data[1] 

521 

522def apply_scale(data, test = False): # apply log scale if test 

523 return log(data) if test else data 

524 

525def reverse_scale(data, test = False): # apply log scale if test 

526 return power10(data) if test else data 

527 

528def replace_none(data, num_data): # replace None elements in data with correspondent in num_data 

529 return [data[i] if data[i] is not None else num_data[i] for i in range(len(data))] 

530 

531numerical = lambda el: not (el is None or math.isnan(el)) or isinstance(el, str) # in the case of string datetimes 

532all_numerical = lambda data: all([numerical(el) for el in data]) 

533 

534def get_lim(data): # it returns the data minimum and maximum limits 

535 data = [el for el in data if numerical(el)] 

536 m = min(data, default = 0) 

537 M = max(data, default = 0) 

538 m, M = (m, M) if m != M else (0.5 * m, 1.5 * m) if m == M != 0 else (-1, 1) 

539 return [m, M] 

540 

541def get_matrix_data(data, lim, bins): # from data to relative canvas coordinates 

542 change = lambda el: 0.5 + (bins - 1) * (el - lim[0]) / (lim[1] - lim[0]) 

543 # round is so that for example 9.9999 = 10, otherwise the floor function will give different results 

544 return [math.floor(round(change(el), 8)) if numerical(el) else el for el in data] 

545 

546def 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 

547 # if len(x) * len(y) == 0: 

548 # return [], [], *[[]] * len(other) 

549 o = transpose(other, len(other)) 

550 xl, yl, ol = [[] for i in range(3)] 

551 for n in range(len(x) - 1): 

552 xn, yn = x[n : n + 2], y[n : n + 2] 

553 xn, yn = get_line(xn, yn) 

554 xl += xn[:-1] 

555 yl += yn[:-1] 

556 ol += [o[n]] * len(xn[:-1]) 

557 xl = xl + [x[-1]] if x != [] else xl 

558 yl = yl + [y[-1]] if x != [] else yl 

559 ol = ol + [o[-1]] if x != [] else ol 

560 return [xl, yl] + transpose(ol, len(other)) 

561 

562def 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. 

563 if not all_numerical(join([x, y])): 

564 return x, y 

565 x0, x1 = x 

566 y0, y1 = y 

567 dx, dy = int(x1) - int(x0), int(y1) - int(y0) 

568 ax, ay = abs(dx), abs(dy) 

569 a = int(max(ax, ay) + 1) 

570 x = [int(el) for el in linspace(x0, x1, a)] 

571 y = [int(el) for el in linspace(y0, y1, a)] 

572 return [x, y] 

573 

574def get_fill_level(fill, lim, bins): 

575 if fill is False: 

576 return False 

577 elif isinstance(fill, str): 

578 return fill 

579 else: 

580 fill = min(max(fill, lim[0]), lim[1]) 

581 fill = get_matrix_data([fill], lim, bins)[0] 

582 return fill 

583 

584def find_filling_values(x, y, y0): 

585 xn, yn, yf = [[]] * 3 

586 l = len(x); 

587 while len(x) > 0: 

588 i = len(xn) 

589 xn.append(x[i]) 

590 yn.append(y[i]) 

591 J = [j for j in range(l) if x[j] == x[i]] 

592 if J != []: 

593 Y = [y[j] for j in J] 

594 j = Y.index(min(Y)) 

595 J.pop(j) 

596 [x.pop(j) for j in J] 

597 [y.pop(j) for j in J] 

598 yf.append(y[j]) 

599 return xn, yn, yf 

600 

601def get_fill_boundaries(x, y): 

602 xm = [] 

603 l = len(x) 

604 for i in range(l): 

605 xi, yi = x[i], y[i] 

606 I = [j for j in range(l) if x[j] == xi and y[j] < yi] 

607 Y = [y[j] for j in I] 

608 m = min(Y, default = yi) 

609 xm.append([x[i], m]) 

610 x, m = transpose(xm) 

611 return m 

612 

613def 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 

614 #y0 = get_fill_boundaries(x, y) 

615 y0 = get_fill_boundaries(x, y) if isinstance(y0, str) else [y0] * len(x) 

616 o = transpose(other, len(other)) 

617 xf, yf, of = [[] for i in range(3)] 

618 xy = [] 

619 for i in range(len(x)): 

620 xi, yi, y0i = x[i], y[i], y0[i] 

621 if [xi, yi] not in xy: 

622 xy.append([xi, yi]) 

623 yn = range(y0i, yi + 1) if y0i < yi else range(yi, y0i) if y0i > yi else [y0i] 

624 yn = list(yn) 

625 xn = [xi] * len(yn) 

626 xf += xn 

627 yf += yn 

628 of += [o[i]] * len(xn) 

629 return [xf, yf] + transpose(of, len(other)) 

630 

631def remove_outsiders(x, y, width, height, *other): 

632 I = [i for i in range(len(x)) if x[i] in range(width) and y[i] in range(height)] 

633 o = transpose(other, len(other)) 

634 return transpose([(x[i], y[i], *o[i]) for i in I], 2 + len(other)) 

635 

636def get_labels(ticks): # it returns the approximated string version of the data ticks 

637 d = distinguishing_digit(ticks) 

638 formatting_string = "{:." + str(d + 1) + "f}" 

639 labels = [formatting_string.format(el) for el in ticks] 

640 pos = [el.index('.') + d + 2 for el in labels] 

641 labels = [labels[i][: pos[i]] for i in range(len(labels))] 

642 all_integers = all(map(lambda el: el == int(el), ticks)) 

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

644 #sign = any([el < 0 for el in ticks]) 

645 #labels = ['+' + labels[i] if ticks[i] > 0 and sign else labels[i] for i in range(len(labels))] 

646 return labels 

647 

648def distinguishing_digit(data): # it return the minimum amount of decimal digits necessary to distinguish all elements of a list 

649 #data = [el for el in data if 'e' not in str(el)] 

650 d = [_distinguishing_digit(data[i], data[i + 1]) for i in range(len(data) - 1)] 

651 return max(d, default = 1) 

652 

653def _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). 

654 d = abs(a - b) 

655 d = 0 if d == 0 else - math.log10(2 * d) 

656 #d = round(d, 10) 

657 d = 0 if d < 0 else math.ceil(d) 

658 d = d + 1 if round(a, d) == round(b, d) else d 

659 return d 

660 

661def add_extra_zeros(label, d): # it adds 0s at the end of a label if necessary 

662 zeros = len(label) - 1 - label.index('.' if 'e' not in label else 'e') 

663 if zeros < d: 

664 label += '0' * (d - zeros) 

665 return label 

666 

667def add_extra_spaces(labels, side): # it adds empty spaces before or after the labels if necessary 

668 length = 0 if labels == [] else max_length(labels) 

669 if side == "left": 

670 labels = [space * (length - len(el)) + el for el in labels] 

671 if side == "right": 

672 labels = [el + space * (length - len(el)) for el in labels] 

673 return labels 

674 

675def hd_group(x, y, xf, yf): # it returns the real coordinates of the HD markers and the matrix that defines the marker 

676 l, xfm, yfm = len(x), max(xf), max(yf) 

677 xm = [el // xfm if numerical(el) else el for el in x] 

678 ym = [el // yfm if numerical(el) else el for el in y] 

679 m = {} 

680 for i in range(l): 

681 xyi = xm[i], ym[i] 

682 xfi, yfi = xf[i], yf[i] 

683 mi = [[0 for x in range(xfi)] for y in range(yfi)] 

684 m[xyi] = mi 

685 for i in range(l): 

686 xyi = xm[i], ym[i] 

687 if all_numerical(xyi): 

688 xk, yk = x[i] % xfi, y[i] % yfi 

689 xk, yk = math.floor(xk), math.floor(yk) 

690 m[xyi][yk][xk] = 1 

691 x, y = transpose(m.keys(), 2) 

692 m = [tuple(join(el[::-1])) for el in m.values()] 

693 return x, y, m 

694 

695############################################### 

696############# Bar Functions ############## 

697############################################### 

698 

699def bars(x, y, width, minimum): # given the bars center coordinates and height, it returns the full bar coordinates 

700 # if x == []: 

701 # return [], [] 

702 bins = len(x) 

703 #bin_size_half = (max(x) - min(x)) / (bins - 1) * width / 2 

704 bin_size_half = width / 2 

705 # adjust the bar width according to the number of bins 

706 if bins > 1: 

707 bin_size_half *= (max(x) - min(x)) / (bins - 1) 

708 xbar, ybar = [], [] 

709 for i in range(bins): 

710 xbar.append([x[i] - bin_size_half, x[i] + bin_size_half]) 

711 ybar.append([minimum, y[i]]) 

712 return xbar, ybar 

713 

714def set_multiple_bar_data(*args): 

715 l = len(args) 

716 Y = [] if l == 0 else args[0] if l == 1 else args[1] 

717 Y = [Y] if not isinstance(Y, list) or len(Y) == 0 else Y 

718 m = len(Y[0]) 

719 x = [] if l == 0 else list(range(1, m + 1)) if l == 1 else args[0] 

720 return x, Y 

721 

722def 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. 

723 #data = [round(el, 15) for el in data] 

724 # if data == []: 

725 # return [], [] 

726 bins = 0 if len(data) == 0 else bins 

727 m, M = min(data, default = 0), max(data, default = 0) 

728 data = [(el - m) / (M - m) * bins if el != M else bins - 1 for el in data] 

729 data = [int(el) for el in data] 

730 histx = linspace(m, M, bins) 

731 histy = [0] * bins 

732 for el in data: 

733 histy[el] += 1 

734 if norm: 

735 histy = [el / len(data) for el in histy] 

736 return histx, histy 

737 

738def single_bar(x, y, ylabel, marker, colors): 

739 l = len(y) 

740 lc = len(colors) 

741 xs = colorize(str(x), 'gray+', 'bold') 

742 bar = [marker * el for el in y] 

743 bar = [apply_ansi(bar[i], colors[i % lc], 1) for i in range(l)] 

744 ylabel = colorize(f'{ylabel:.2f}', 'gray+', 'bold') 

745 bar = xs + space + ''.join(bar) + space + ylabel 

746 return bar 

747 

748def bar_data(*args, width = None, mode = 'stacked'): 

749 x, Y = set_multiple_bar_data(*args) 

750 x = list(map(str, x)) 

751 x = add_extra_spaces(x, 'right') 

752 lx = len(x[0]) 

753 y = [sum(el) for el in transpose(Y)] if mode == 'stacked' else Y 

754 ly = max_length([round(el, 2) for el in join(y)]) 

755 

756 width_term = terminal_width() 

757 width = width_term if width is None else min(width, width_term) 

758 width = max(width, lx + ly + 2 + 1) 

759 

760 my = max(join(y)) 

761 my = 1 if my == 0 else my 

762 dx = my / (width - lx - ly - 2) 

763 Yi = [[round(el / dx, 0) for el in y] for y in Y] 

764 Yi = transpose(Yi) 

765 

766 return x, y, Yi, width 

767 

768def correct_marker(marker = None): 

769 return simple_bar_marker if marker is None else marker[0] 

770 

771def get_title(title, width): 

772 out = '' 

773 if title is not None: 

774 l = len(uncolorize(title)) 

775 w1 = (width - 2 - l) // 2; w2 = width - l - 2 - w1 

776 l1 = '─' * w1 + space 

777 l2 = space + '─' * w2 

778 out = colorize(l1 + title + l2, 'gray+', 'bold') + '\n' 

779 return out 

780 

781def get_simple_labels(marker, labels, colors, width): 

782 out = '\n' 

783 if labels != None: 

784 l = len(labels) 

785 lc = len(colors) 

786 out = space.join([colorize(marker * 3, colors[i % lc]) + space + colorize(labels[i], 'gray+', 'bold') for i in range(l)]) 

787 out = '\n' + get_title(out, width) 

788 return out 

789 

790############################################### 

791############# Box Functions ############## 

792############################################### 

793 

794def box(x, y, width, minimum): # given the bars center coordinates and height, it returns the full bar coordinates 

795 # if x == []: 

796 # return [], [] 

797 bins = len(x) 

798 #bin_size_half = (max(x) - min(x)) / (bins - 1) * width / 2 

799 bin_size_half = width / 2 

800 # adjust the bar width according to the number of bins 

801 if bins > 1: 

802 bin_size_half *= (max(x) - min(x)) / (bins - 1) 

803 c, q1, q2, q3, h, l = [], [], [], [], [], [] 

804 xbar, ybar, mybar = [], [], [] 

805 

806 for i in range(bins): 

807 c.append(x[i]) 

808 xbar.append([x[i] - bin_size_half, x[i] + bin_size_half]) 

809 q1.append(quantile(y[i], 0.25)) 

810 q2.append(quantile(y[i], 0.50)) 

811 q3.append(quantile(y[i], 0.75)) 

812 h.append(max(y[i])) 

813 l.append(min(y[i])) 

814 

815 return q1, q2, q3, h, l, c, xbar 

816 

817############################################## 

818########## Image Utilities ############# 

819############################################## 

820 

821def 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 

822 size_old = [size_old[0], size_old[1] / 2] 

823 ratio_old = size_old[1] / size_old[0] 

824 size_new = replace(size_new, size_old) 

825 ratio_new = size_new[1] / size_new[0] 

826 #ratio_new = size_new[1] / size_new[0] 

827 size_new = [1 if el == 0 else el for el in size_new] 

828 return [int(size_new[0]), int(size_new[1])] 

829 

830def image_to_matrix(image): # from image to a matrix of pixels 

831 pixels = list(image.getdata()) 

832 width, height = image.size 

833 return [pixels[i * width:(i + 1) * width] for i in range(height)]