plotext-plus 1.0.8__py3-none-any.whl → 1.0.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
plotext_plus/_utility.py CHANGED
@@ -1,33 +1,50 @@
1
- import sys, shutil, os, re, math, inspect
2
- from plotext_plus._dict import *
1
+ import inspect
2
+ import math
3
+ import os
4
+ import re
5
+ import shutil
6
+ import sys
7
+
8
+ from plotext_plus._dict import * # noqa: F403
3
9
 
4
10
  ###############################################
5
11
  ######### Number Manipulation ##########
6
12
  ###############################################
7
13
 
8
- def round(n, d = 0): # the standard round(0.5) = 0 instead of 1; this version rounds 0.5 to 1
9
- n *= 10 ** d
14
+
15
+ def round(
16
+ n, d=0
17
+ ): # the standard round(0.5) = 0 instead of 1; this version rounds 0.5 to 1
18
+ n *= 10**d
10
19
  f = math.floor(n)
11
20
  r = f if n - f < 0.5 else math.ceil(n)
12
21
  return r * 10 ** (-d)
13
22
 
14
- 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
15
- return ((x ** p + y ** p) / 2) ** (1 / p)
16
23
 
17
- def replace(data, data2, element = None): # replace element in data with correspondent in data2 when element is found
24
+ def mean(
25
+ x, y, p=1
26
+ ): # 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
27
+ return ((x**p + y**p) / 2) ** (1 / p)
28
+
29
+
30
+ def replace(
31
+ data, data2, element=None
32
+ ): # replace element in data with correspondent in data2 when element is found
18
33
  res = []
19
34
  for i in range(len(data)):
20
35
  el = data[i] if data[i] != element else data2[i]
21
36
  res.append(el)
22
37
  return res
23
38
 
24
- def try_float(data): # it turn a string into float if it can
39
+
40
+ def try_float(data):
25
41
  try:
26
42
  return float(data)
27
- except:
43
+ except (ValueError, TypeError):
28
44
  return data
29
45
 
30
- def quantile(data, q): # calculate the quantile of a given array
46
+
47
+ def quantile(data, q): # calculate the quantile of a given array
31
48
  data = sorted(data)
32
49
  index = q * (len(data) - 1)
33
50
  if index.is_integer():
@@ -35,183 +52,259 @@ def quantile(data, q): # calculate the quantile of a given array
35
52
  else:
36
53
  return (data[int(index)] + data[int(index) + 1]) / 2
37
54
 
55
+
38
56
  ###############################################
39
57
  ########### List Creation ##############
40
58
  ###############################################
41
59
 
42
- def linspace(lower, upper, length = 10): # it returns a lists of numbers from lower to upper with given length
60
+
61
+ def linspace(
62
+ lower, upper, length=10
63
+ ): # it returns a lists of numbers from lower to upper with given length
43
64
  slope = (upper - lower) / (length - 1) if length > 1 else 0
44
65
  return [lower + x * slope for x in range(length)]
45
66
 
46
- def sin(periods = 2, length = 200, amplitude = 1, phase = 0, decay = 0): # sinusoidal data with given parameters
67
+
68
+ def sin(
69
+ periods=2, length=200, amplitude=1, phase=0, decay=0
70
+ ): # sinusoidal data with given parameters
47
71
  f = 2 * math.pi * periods / (length - 1)
48
- phase = math.pi * phase
72
+ phase = math.pi * phase
49
73
  d = decay / length
50
- return [amplitude * math.sin(f * el + phase) * math.exp(- d * el) for el in range(length)]
74
+ return [
75
+ amplitude * math.sin(f * el + phase) * math.exp(-d * el) for el in range(length)
76
+ ]
51
77
 
52
- def square(periods = 2, length = 200, amplitude = 1):
53
- T = length / periods
54
- step = lambda t: amplitude if t % T <= T / 2 else - amplitude
78
+
79
+ def square(periods=2, length=200, amplitude=1):
80
+ period_length = length / periods
81
+ def step(t):
82
+ return amplitude if t % period_length <= period_length / 2 else -amplitude
55
83
  return [step(i) for i in range(length)]
56
84
 
57
- 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]
85
+
86
+ def to_list(
87
+ data, length
88
+ ): # eg: to_list(1, 3) = [1, 1 ,1]; to_list([1,2,3], 6) = [1, 2, 3, 1, 2, 3]
58
89
  data = data if isinstance(data, list) else [data] * length
59
90
  data = data * math.ceil(length / len(data)) if len(data) > 0 else []
60
- return data[ : length]
91
+ return data[:length]
92
+
61
93
 
62
- def difference(data1, data2) : # elements in data1 not in date2
94
+ def difference(data1, data2): # elements in data1 not in date2
63
95
  return [el for el in data1 if el not in data2]
64
96
 
97
+
65
98
  ###############################################
66
99
  ######### List Transformation ##########
67
100
  ###############################################
68
101
 
69
- def 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
102
 
72
- def power10(data): # it apply log function to the data
73
- return [10 ** el for el in data]
103
+ def log(data): # it apply log function to the data
104
+ return (
105
+ [math.log10(el) for el in data] if isinstance(data, list) else math.log10(data)
106
+ )
107
+
74
108
 
75
- def floor(data): # it floors a list of data
109
+ def power10(data): # it apply log function to the data
110
+ return [10**el for el in data]
111
+
112
+
113
+ def floor(data): # it floors a list of data
76
114
  return list(map(math.floor, data))
77
115
 
78
- def 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]
116
+
117
+ def repeat(data, length): # repeat the same data till length is reached
118
+ data_len = len(data) if isinstance(data, list) else 1
119
+ data = join([data] * math.ceil(length / data_len))
120
+ return data[:length]
121
+
82
122
 
83
123
  ###############################################
84
124
  ########## List Manipulation ###########
85
125
  ###############################################
86
126
 
87
- def 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
127
 
91
- def 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])]
128
+ def no_duplicates(data): # removes duplicates from a list
129
+ return list(set(data))
130
+ # return list(dict.fromkeys(data)) # it takes double time
94
131
 
95
- def cumsum(data): # it returns the cumulative sums of a list; eg: cumsum([0,1,2,3,4]) = [0,1,3,6,10]
132
+
133
+ def join(data): # flatten lists at first level
134
+ # return [el for row in data for el in row]
135
+ return [el for row in data for el in (join(row) if isinstance(row, list) else [row])]
136
+
137
+
138
+ def cumsum(
139
+ data,
140
+ ): # it returns the cumulative sums of a list; eg: cumsum([0,1,2,3,4]) = [0,1,3,6,10]
96
141
  s = [0]
97
142
  for i in range(len(data)):
98
143
  s.append(s[-1] + data[i])
99
144
  return s[1:]
100
145
 
146
+
101
147
  ###############################################
102
148
  ######### Matrix Manipulation ##########
103
149
  ###############################################
104
150
 
151
+
105
152
  def matrix_size(matrix): # cols, height
106
153
  return [len(matrix[0]), len(matrix)] if matrix != [] else [0, 0]
107
154
 
108
- def transpose(data, length = 1): # it needs no explanation
109
- return [[]] * length if data == [] else list(map(list, zip(*data)))
110
155
 
111
- def vstack(matrix, extra): # vertical stack of two matrices
112
- return extra + matrix # + extra
156
+ def transpose(data, length=1): # it needs no explanation
157
+ return [[]] * length if data == [] else list(map(list, zip(*data, strict=False)))
113
158
 
114
- def hstack(matrix, extra): # horizontal stack of two matrices
159
+
160
+ def vstack(matrix, extra): # vertical stack of two matrices
161
+ return extra + matrix # + extra
162
+
163
+
164
+ def hstack(matrix, extra): # horizontal stack of two matrices
115
165
  lm, le = len(matrix), len(extra)
116
- l = max(lm, le)
117
- return [matrix[i] + extra[i] for i in range(l)]
118
-
119
- def 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
-
124
- def 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))
166
+ max_length = max(lm, le)
167
+ return [matrix[i] + extra[i] for i in range(max_length)]
168
+
169
+
170
+ def turn_gray(matrix): # it takes a standard matrix and turns it into an grayscale one
171
+ max_val, m = max(join(matrix), default=0), min(join(matrix), default=0)
172
+ def to_gray(el):
173
+ return (
174
+ tuple([int(255 * (el - m) / (max_val - m))] * 3) if m != max_val else (127, 127, 127)
175
+ )
176
+ return [[to_gray(el) for el in row] for row in matrix]
177
+
178
+
179
+ def brush(*lists): # remove duplicates from lists x, y, z ...
180
+ min_length = min(map(len, lists))
181
+ lists = [el[:min_length] for el in lists]
182
+ z = list(zip(*lists, strict=False))
128
183
  z = no_duplicates(z)
129
- #z = sorted(z)#, key = lambda x: x[0])
184
+ # z = sorted(z)#, key = lambda x: x[0])
130
185
  lists = transpose(z, len(lists))
131
186
  return lists
132
187
 
188
+
133
189
  ###############################################
134
190
  ######### String Manipulation ###########
135
191
  ###############################################
136
192
 
137
193
  nl = "\n"
138
194
 
139
- def 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
195
 
142
- def format_time(time): # it properly formats the computational time
196
+ def only_spaces(
197
+ string,
198
+ ): # it returns True if string is made of only empty spaces or is None or ''
199
+ return isinstance(string, str) and (
200
+ string == len(string) * space
201
+ ) # and len(string) != 0
202
+
203
+
204
+ def format_time(time): # it properly formats the computational time
143
205
  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)))
206
+ unit = "s" if t >= 1 else "ms" if t >= 10**-3 else "µs"
207
+ p = 0 if unit == "s" else 3 if unit == "ms" else 6
208
+ t = round(10**p * t, 1)
209
+ str_length = len(str(int(t)))
148
210
  t = str(t)
149
- #t = ' ' * (3 - l) + t
150
- return t[ : l + 2] + ' ' + unit
211
+ # t = ' ' * (3 - str_length) + t
212
+ return t[: str_length + 2] + " " + unit
213
+
214
+
215
+ positive_color = "green+"
216
+ negative_color = "red"
217
+ title_color = "cyan+"
218
+
151
219
 
152
- positive_color = 'green+'
153
- negative_color = 'red'
154
- title_color = 'cyan+'
220
+ def format_strings(
221
+ string1, string2, color=positive_color
222
+ ): # returns string1 in bold and with color + string2 with a pre-formatted style
223
+ return colorize(string1, color, "bold") + " " + colorize(string2, style=info_style)
155
224
 
156
- def 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
225
 
159
- 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
160
- l = len(label)
161
- b, e = max(coord - l + 1, 0), min(coord + l, len(string) - 1)
226
+ def correct_coord(
227
+ string, label, coord
228
+ ): # 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
229
+ label_length = len(label)
230
+ b, e = max(coord - label_length + 1, 0), min(coord + label_length, len(string) - 1)
162
231
  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
232
+ b, e = min(data, default=coord - label_length + 1), max(data, default=coord + label_length)
233
+ b, e = e - label_length + 1, b + label_length
234
+ return (b + e - label_length) // 2
166
235
 
167
- def no_char_duplicates(string, char): # it remove char duplicates from string
168
- pattern = char + '{2,}'
236
+
237
+ def no_char_duplicates(string, char): # it remove char duplicates from string
238
+ pattern = char + "{2,}"
169
239
  string = re.sub(pattern, char, string)
170
240
  return string
171
241
 
172
- def read_lines(text, delimiter = None, columns = None): # from a long text to well formatted data
242
+
243
+ def read_lines(
244
+ text, delimiter=None, columns=None
245
+ ): # from a long text to well formatted data
173
246
  delimiter = " " if delimiter is None else delimiter
174
247
  data = []
175
- columns = len(no_char_duplicates(text[0], delimiter).split(delimiter)) if columns is None else columns
248
+ columns = (
249
+ len(no_char_duplicates(text[0], delimiter).split(delimiter))
250
+ if columns is None
251
+ else columns
252
+ )
176
253
  for i in range(len(text)):
177
254
  row = text[i]
178
255
  row = no_char_duplicates(row, delimiter)
179
256
  row = row.split(delimiter)
180
- row = [el.replace('\n', '') for el in row]
257
+ row = [el.replace("\n", "") for el in row]
181
258
  cols = len(row)
182
- row = [row[col].replace('\n', '') if col in range(cols) else '' for col in range(columns)]
259
+ row = [
260
+ row[col].replace("\n", "") if col in range(cols) else ""
261
+ for col in range(columns)
262
+ ]
183
263
  row = [try_float(el) for el in row]
184
264
  data.append(row)
185
265
  return data
186
266
 
187
- def pad_string(num, length): # pad a number with spaces before to reach length
267
+
268
+ def pad_string(num, length): # pad a number with spaces before to reach length
188
269
  num = str(num)
189
- l = len(num)
190
- return num + ' ' * (length - l)
270
+ num_length = len(num)
271
+ return num + " " * (length - num_length)
272
+
191
273
 
192
274
  def max_length(strings):
193
275
  strings = map(str, strings)
194
- return max(map(len, strings), default = 0)
276
+ return max(map(len, strings), default=0)
277
+
195
278
 
196
279
  ###############################################
197
280
  ########## File Manipulation ############
198
281
  ###############################################
199
282
 
283
+
200
284
  def correct_path(path):
201
285
  folder, base = os.path.dirname(path), os.path.basename(path)
202
- folder = os.path.expanduser("~") if folder in ['', '~'] else folder
286
+ folder = os.path.expanduser("~") if folder in ["", "~"] else folder
203
287
  path = os.path.join(folder, base)
204
288
  return path
205
289
 
206
- def is_file(path, log = True): # returns True if path exists
290
+
291
+ def is_file(path, log=True): # returns True if path exists
207
292
  res = os.path.isfile(path)
208
- print(format_strings("not a file:", path, negative_color)) if not res and log else None
293
+ (
294
+ print(format_strings("not a file:", path, negative_color))
295
+ if not res and log
296
+ else None
297
+ )
209
298
  return res
210
299
 
211
- def script_folder(): # the folder of the script executed
300
+
301
+ def script_folder(): # the folder of the script executed
212
302
  return parent_folder(inspect.getfile(sys._getframe(1)))
213
303
 
214
- 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
304
+
305
+ def parent_folder(
306
+ path, level=1
307
+ ): # it return the parent folder of the path or file given; if level is higher then 1 the process is iterated
215
308
  if level <= 0:
216
309
  return path
217
310
  elif level == 1:
@@ -219,27 +312,36 @@ def parent_folder(path, level = 1): # it return the parent folder of the path or
219
312
  else:
220
313
  return parent_folder(parent_folder(path, level - 1))
221
314
 
222
- 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
315
+
316
+ def join_paths(
317
+ *args,
318
+ ): # 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
319
  args = list(args)
224
- args[0] = _correct_path(args[0]) if args[0] == "~" else args[0]
320
+ args[0] = correct_path(args[0]) if args[0] == "~" else args[0]
225
321
  return os.path.abspath(os.path.join(*args))
226
322
 
227
- def delete_file(path, log = True): # remove the file if it exists
323
+
324
+ def delete_file(path, log=True): # remove the file if it exists
228
325
  path = correct_path(path)
229
326
  if is_file(path):
230
327
  os.remove(path)
231
328
  print(format_strings("file removed:", path, negative_color)) if log else None
232
329
 
233
- def read_data(path, delimiter = None, columns = None, first_row = None, log = True): # it turns a text file into data lists
330
+
331
+ def read_data(
332
+ path, delimiter=None, columns=None, first_row=None, log=True
333
+ ): # it turns a text file into data lists
234
334
  path = correct_path(path)
235
335
  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()
336
+ with open(path) as file:
337
+ text = file.readlines()[first_row:]
239
338
  print(format_strings("data read from", path)) if log else None
240
339
  return read_lines(text, delimiter, columns)
241
340
 
242
- def write_data(data, path, delimiter = None, columns = None, log = True): # it turns a matrix into a text file
341
+
342
+ def write_data(
343
+ data, path, delimiter=None, columns=None, log=True
344
+ ): # it turns a matrix into a text file
243
345
  delimiter = " " if delimiter is None else delimiter
244
346
  cols = len(data[0])
245
347
  cols = range(1, cols + 1) if columns is None else columns
@@ -247,158 +349,217 @@ def write_data(data, path, delimiter = None, columns = None, log = True): # it t
247
349
  for row in data:
248
350
  row = [row[i - 1] for i in cols]
249
351
  row = list(map(str, row))
250
- text += delimiter.join(row) + '\n'
251
- save_text(text, path, log = log)
352
+ text += delimiter.join(row) + "\n"
353
+ save_text(text, path, log=log)
354
+
252
355
 
253
- def save_text(text, path, append = False, log = True): # it saves some text to the path selected
356
+ def save_text(
357
+ text, path, append=False, log=True
358
+ ): # it saves some text to the path selected
254
359
  path = correct_path(path)
255
360
  mode = "a" if append else "w+"
256
- with open(path , mode, encoding='utf-8') as file:
361
+ with open(path, mode, encoding="utf-8") as file:
257
362
  file.write(text)
258
363
  print(format_strings("text saved in", path)) if log else None
259
364
 
260
- def download(url, path, log = True): # it download the url (image, video, gif etc) to path
365
+
366
+ def download(
367
+ url, path, log=True
368
+ ): # it download the url (image, video, gif etc) to path
369
+ from urllib.parse import urlparse
261
370
  from urllib.request import urlretrieve
371
+
372
+ # Validate URL scheme for security (B310)
373
+ parsed_url = urlparse(url)
374
+ allowed_schemes = {'http', 'https'}
375
+ if parsed_url.scheme.lower() not in allowed_schemes:
376
+ raise ValueError(f"URL scheme '{parsed_url.scheme}' not allowed. Only {allowed_schemes} are permitted.")
377
+
378
+ # Validate URL has a network location
379
+ if not parsed_url.netloc:
380
+ raise ValueError("Invalid URL: missing network location")
381
+
262
382
  path = correct_path(path)
263
- urlretrieve(url, path)
264
- print(format_strings('url saved in', path)) if log else None
383
+ urlretrieve(url, path) # noqa: S310 # URL scheme already validated above
384
+ print(format_strings("url saved in", path)) if log else None
385
+
265
386
 
266
387
  ###############################################
267
388
  ######### Platform Utilities ############
268
389
  ###############################################
269
390
 
270
- def is_ipython(): # true if running in ipython shenn
391
+
392
+ def is_ipython() -> bool: # true if running in ipython shenn
271
393
  try:
272
- __IPYTHON__
394
+ __IPYTHON__ # noqa: B018 # Intentional check for IPython existence
273
395
  return True
274
396
  except NameError:
275
397
  return False
276
398
 
277
- def 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'
399
+
400
+ def platform() -> str: # the platform (unix or windows) you are using plotext in
401
+ platform = sys.platform
402
+ if platform in {"win32", "cygwin"}:
403
+ return "windows"
404
+ else:
405
+ return "unix"
406
+
283
407
 
284
408
  platform = platform()
285
409
 
286
- # to enable ascii escape color sequences
410
+ # to enable ascii escape color sequences on Windows
287
411
  if platform == "windows":
288
- import subprocess
289
- subprocess.call('', shell = True)
412
+ import os
413
+ # Enable ANSI escape sequences on Windows
414
+ # This is safer than using subprocess with shell=True
415
+ os.system("") # nosec B605 # noqa: S605,S607 - minimal safe command for Windows ANSI enabling
290
416
 
291
- def terminal_size(): # it returns the terminal size as [width, height]
417
+
418
+ def terminal_size(): # it returns the terminal size as [width, height]
292
419
  try:
293
420
  size = shutil.get_terminal_size()
294
421
  return list(size)
295
422
  except OSError:
296
423
  return [None, None]
297
424
 
298
- def terminal_width(): # returns terminal width, adjusted for banner borders when banners are enabled
425
+
426
+ def terminal_width(): # returns terminal width, adjusted for banner borders when banners are enabled
299
427
  width = terminal_size()[0]
300
428
  if width is None:
301
429
  return None
302
-
430
+
303
431
  # Check if banners are enabled by checking the global output instance
304
432
  try:
305
433
  from plotext_plus._output import get_output_instance
434
+
306
435
  output_instance = get_output_instance()
307
- if hasattr(output_instance, 'use_banners') and output_instance.use_banners:
436
+ if hasattr(output_instance, "use_banners") and output_instance.use_banners:
308
437
  # Rich Panel with borders and padding=(0, 1) uses:
309
438
  # - 2 characters for left/right borders
310
439
  # - 2 characters for padding (1 on each side)
311
440
  # Total: 4 characters of horizontal space
312
441
  return max(width - 4, 1) # Ensure minimum width of 1
313
- except:
442
+ except (ImportError, AttributeError):
314
443
  pass # If anything fails, fall back to original width
315
-
444
+
316
445
  return width
317
446
 
447
+
318
448
  tw = terminal_width
319
449
 
320
- terminal_height = lambda: terminal_size()[1]
450
+ def terminal_height():
451
+ return terminal_size()[1]
321
452
  th = terminal_height
322
453
 
323
- def clear_terminal(lines = None): # it cleat the entire terminal, or the specified number of lines
454
+
455
+ def clear_terminal(
456
+ lines=None,
457
+ ): # it cleat the entire terminal, or the specified number of lines
324
458
  if lines is None:
325
- write('\033c')
459
+ write("\033c")
326
460
  else:
327
- for r in range(lines):
328
- write("\033[A") # moves the curson up
329
- write("\033[2K") # clear the entire line
461
+ for _r in range(lines):
462
+ write("\033[A") # moves the curson up
463
+ write("\033[2K") # clear the entire line
464
+
330
465
 
331
- def write(string): # the print function used by plotext - now uses chuk-term backend
466
+ def write(string): # the print function used by plotext - now uses chuk-term backend
332
467
  from plotext_plus._output import write as output_write
468
+
333
469
  output_write(string)
334
470
 
335
- class memorize: # it memorise the arguments of a function, when used as its decorator, to reduce computational time
471
+
472
+ class Memorize: # it memorise the arguments of a function, when used as its decorator, to reduce computational time
336
473
  def __init__(self, f):
337
474
  self.f = f
338
475
  self.memo = {}
476
+
339
477
  def __call__(self, *args):
340
- if not args in self.memo:
478
+ if args not in self.memo:
341
479
  self.memo[args] = self.f(*args)
342
480
  return self.memo[args]
343
481
 
482
+
344
483
  ##############################################
345
484
  ######### Marker Utilities ###########
346
485
  ##############################################
347
486
 
348
- space = ' ' # the default null character that appears as background to all plots
349
- plot_marker = "hd" if platform == 'unix' else 'dot'
487
+ space = " " # the default null character that appears as background to all plots
488
+ plot_marker = "hd" if platform == "unix" else "dot"
350
489
 
351
- hd_markers = {hd_codes[el] : el for el in hd_codes}
352
- fhd_markers = {fhd_codes[el] : el for el in fhd_codes}
353
- braille_markers = {braille_codes[el] : el for el in braille_codes}
354
- simple_bar_marker = ''
490
+ hd_markers = {hd_codes[el]: el for el in hd_codes}
491
+ fhd_markers = {fhd_codes[el]: el for el in fhd_codes}
492
+ braille_markers = {braille_codes[el]: el for el in braille_codes}
493
+ simple_bar_marker = ""
355
494
 
356
- @memorize
495
+
496
+ @Memorize
357
497
  def get_hd_marker(code):
358
- return hd_codes[code] if len(code) == 4 else fhd_codes[code] if len(code) == 6 else braille_codes[code]
498
+ return (
499
+ hd_codes[code]
500
+ if len(code) == 4
501
+ else fhd_codes[code] if len(code) == 6 else braille_codes[code]
502
+ )
503
+
504
+
505
+ def marker_factor(
506
+ marker, hd, fhd, braille
507
+ ): # useful to improve the resolution of the canvas for higher resolution markers
508
+ return (
509
+ hd
510
+ if marker == "hd"
511
+ else fhd if marker == "fhd" else braille if marker == "braille" else 1
512
+ )
359
513
 
360
- def marker_factor(marker, hd, fhd, braille): # useful to improve the resolution of the canvas for higher resolution markers
361
- return hd if marker == 'hd' else fhd if marker == 'fhd' else braille if marker == 'braille' else 1
362
514
 
363
515
  ##############################################
364
516
  ########### Color Utilities ############
365
517
  ##############################################
366
518
 
367
519
  # A user could specify three types of colors
368
- # an integer for 256 color codes
369
- # a tuple for RGB color codes
370
- # a string for 16 color codes or styles
520
+ # an integer for 256 color codes
521
+ # a tuple for RGB color codes
522
+ # a string for 16 color codes or styles
371
523
 
372
524
  # Along side the user needs to specify whatever it is for background / fullground / style
373
525
  # which plotext calls 'character' = 0 / 1 / 2
374
526
 
375
527
 
376
- #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]
528
+ # 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]
377
529
 
378
- def get_color_code(color): # the color number code from color string
530
+
531
+ def get_color_code(color): # the color number code from color string
379
532
  color = color.strip()
380
533
  return color_codes[color]
381
534
 
382
- def get_color_name(code): # the color string from color number code
535
+
536
+ def get_color_name(code): # the color string from color number code
383
537
  codes = list(color_codes.values())
384
538
  return colors[codes.index(code)] if code in codes else no_color
385
539
 
540
+
386
541
  def is_string_color(color):
387
542
  return isinstance(color, str) and color.strip() in colors
388
543
 
544
+
389
545
  def is_integer_color(color):
390
546
  return isinstance(color, int) and 0 <= color <= 255
391
547
 
548
+
392
549
  def is_rgb_color(color):
393
- is_rgb = isinstance(color, list) or isinstance(color, tuple)
550
+ is_rgb = isinstance(color, (list, tuple))
394
551
  is_rgb = is_rgb and len(color) == 3
395
- is_rgb = is_rgb and all([is_integer_color(el) for el in color])
552
+ is_rgb = is_rgb and all(is_integer_color(el) for el in color)
396
553
  return is_rgb
397
554
 
555
+
398
556
  def is_color(color):
399
557
  return is_string_color(color) or is_integer_color(color) or is_rgb_color(color)
400
558
 
401
- def colorize(string, color = None, style = None, background = None, show = False): # it paints a text with given fullground and background color
559
+
560
+ def colorize(
561
+ string, color=None, style=None, background=None, show=False
562
+ ): # it paints a text with given fullground and background color
402
563
  string = apply_ansi(string, background, 0)
403
564
  string = apply_ansi(string, color, 1)
404
565
  string = apply_ansi(string, style, 2)
@@ -406,164 +567,209 @@ def colorize(string, color = None, style = None, background = None, show = False
406
567
  print(string)
407
568
  return string
408
569
 
409
- def uncolorize(string): # remove color codes from colored string
410
- colored = lambda: ansi_begin in string
570
+
571
+ def uncolorize(string): # remove color codes from colored string
572
+ def colored():
573
+ return ansi_begin in string
411
574
  while colored():
412
575
  b = string.index(ansi_begin)
413
- e = string[b : ].index('m') + b + 1
414
- string = string.replace(string[b : e], '')
576
+ e = string[b:].index("m") + b + 1
577
+ string = string.replace(string[b:e], "")
415
578
  return string
416
579
 
580
+
417
581
  def apply_ansi(string, color, character):
418
582
  begin, end = ansi(color, character)
419
583
  return begin + string + end
420
584
 
421
- #ansi_begin = '\033['
422
- ansi_begin = '\x1b['
423
- ansi_end = ansi_begin + '0m'
424
585
 
425
- @memorize
586
+ # ansi_begin = '\033['
587
+ ansi_begin = "\x1b["
588
+ ansi_end = ansi_begin + "0m"
589
+
590
+
591
+ @Memorize
426
592
  def colors_to_ansi(fullground, style, background):
427
593
  color = [background, fullground, style]
428
- return ''.join([ansi(color[i], i)[0] for i in range(3)])
594
+ return "".join([ansi(color[i], i)[0] for i in range(3)])
429
595
 
430
- @memorize
596
+
597
+ @Memorize
431
598
  def ansi(color, character):
432
599
  if color == no_color:
433
- return ['', '']
434
- col, fg, tp = '', '', ''
600
+ return ["", ""]
601
+ col, fg, tp = "", "", ""
435
602
  if character == 2 and is_style(color):
436
603
  col = get_style_codes(color)
437
- col = ';'.join([str(el) for el in col])
604
+ col = ";".join([str(el) for el in col])
438
605
  elif character != 2:
439
- fg = '38;' if character == 1 else '48;'
440
- tp = '5;'
606
+ fg = "38;" if character == 1 else "48;"
607
+ tp = "5;"
441
608
  if is_string_color(color):
442
609
  col = str(get_color_code(color))
443
610
  elif is_integer_color(color):
444
611
  col = str(color)
445
612
  elif is_rgb_color(color):
446
- col = ';'.join([str(el) for el in color])
447
- tp = '2;'
448
- is_color = col != ''
449
- begin = ansi_begin + fg + tp + col + 'm' if is_color else ''
450
- end = ansi_end if is_color else ''
613
+ col = ";".join([str(el) for el in color])
614
+ tp = "2;"
615
+ is_color = col != ""
616
+ begin = ansi_begin + fg + tp + col + "m" if is_color else ""
617
+ end = ansi_end if is_color else ""
451
618
  return [begin, end]
452
619
 
620
+
453
621
  ## 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
454
622
 
623
+
455
624
  def to_rgb(color):
456
- if is_string_color(color): # from 0 to 1
625
+ if is_string_color(color): # from 0 to 1
457
626
  color = get_color_code(color)
458
- #color = type0_to_type1_codes[code]
459
- if is_integer_color(color): # from 0 or 1 to 2
627
+ # color = type0_to_type1_codes[code]
628
+ if is_integer_color(color): # from 0 or 1 to 2
460
629
  return type1_to_type2_codes[color]
461
630
  return color
462
631
 
632
+
463
633
  ##############################################
464
634
  ############ Style Codes ##############
465
635
  ##############################################
466
636
 
467
- no_style = 'default'
637
+ no_style = "default"
468
638
 
469
639
  styles = list(style_codes.keys()) + [no_style]
470
640
 
471
- info_style = 'dim'
641
+ info_style = "dim"
472
642
 
473
- def get_style_code(style): # from single style to style number code
643
+
644
+ def get_style_code(style): # from single style to style number code
474
645
  style = style.strip()
475
646
  return style_codes[style]
476
647
 
477
- def get_style_codes(style): # from many styles (separated by space) to as many number codes
648
+
649
+ def get_style_codes(
650
+ style,
651
+ ): # from many styles (separated by space) to as many number codes
478
652
  style = style.strip().split()
479
653
  codes = [get_style_code(el) for el in style if el in styles]
480
654
  codes = no_duplicates(codes)
481
655
  return codes
482
656
 
483
- def get_style_name(code): # from style number code to style name
657
+
658
+ def get_style_name(code): # from style number code to style name
484
659
  codes = list(style_codes.values())
485
660
  return styles[codes.index(code)] if code in codes else no_style
486
661
 
487
- def clean_styles(style): # it returns a well written sequence of styles (separated by spaces) from a possible confused one
662
+
663
+ def clean_styles(
664
+ style,
665
+ ): # it returns a well written sequence of styles (separated by spaces) from a possible confused one
488
666
  codes = get_style_codes(style)
489
- return ' '.join([get_style_name(el) for el in codes])
667
+ return " ".join([get_style_name(el) for el in codes])
668
+
490
669
 
491
670
  def is_style(style):
492
- style = style.strip().split() if isinstance(style, str) else ['']
493
- return any([el in styles for el in style])
671
+ style = style.strip().split() if isinstance(style, str) else [""]
672
+ return any(el in styles for el in style)
673
+
494
674
 
495
675
  ##############################################
496
676
  ########### Plot Utilities ############
497
677
  ##############################################
498
678
 
499
- def set_data(x = None, y = None): # it return properly formatted x and y data lists
500
- if x is None and y is None :
501
- x, y = [], []
502
- elif x is not None and y is None:
503
- y = x
504
- x = list(range(1, len(y) + 1))
505
- lx, ly = len(x), len(y)
506
- if lx != ly:
507
- l = min(lx, ly)
508
- x = x[ : l]
509
- y = y[ : l]
510
- return [list(x), list(y)]
679
+
680
+ def set_data(x=None, y=None): # it return properly formatted x and y data lists
681
+ if x is None and y is None:
682
+ x, y = [], []
683
+ elif x is not None and y is None:
684
+ y = x
685
+ x = list(range(1, len(y) + 1))
686
+ lx, ly = len(x), len(y)
687
+ if lx != ly:
688
+ min_length = min(lx, ly)
689
+ x = x[:min_length]
690
+ y = y[:min_length]
691
+ return [list(x), list(y)]
692
+
511
693
 
512
694
  ##############################################
513
695
  ####### Figure Class Utilities ########
514
696
  ##############################################
515
697
 
516
- def set_sizes(sizes, size_max): # given certain widths (or heights) - some of them are None - it sets them so to respect max value
698
+
699
+ def set_sizes(
700
+ sizes, size_max
701
+ ): # given certain widths (or heights) - some of them are None - it sets them so to respect max value
517
702
  bins = len(sizes)
518
703
  for s in range(bins):
519
- size_set = sum([el for el in sizes[0 : s] + sizes[s + 1 : ] if el is not None])
704
+ size_set = sum([el for el in sizes[0:s] + sizes[s + 1 :] if el is not None])
520
705
  available = max(size_max - size_set, 0)
521
- to_set = len([el for el in sizes[s : ] if el is None])
706
+ to_set = len([el for el in sizes[s:] if el is None])
522
707
  sizes[s] = available // to_set if sizes[s] is None else sizes[s]
523
708
  return sizes
524
709
 
525
- 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
710
+
711
+ def fit_sizes(
712
+ sizes, size_max
713
+ ): # 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
526
714
  bins = len(sizes)
527
715
  s = bins - 1
528
- #while (sum(sizes) != size_max if not_less else sum(sizes) > size_max) and s >= 0:
716
+ # while (sum(sizes) != size_max if not_less else sum(sizes) > size_max) and s >= 0:
529
717
  while sum(sizes) > size_max and s >= 0:
530
718
  other_sizes = sum([sizes[i] for i in range(bins) if i != s])
531
719
  sizes[s] = max(size_max - other_sizes, 0)
532
720
  s -= 1
533
721
  return sizes
534
722
 
723
+
535
724
  ##############################################
536
725
  ####### Build Class Utilities #########
537
726
  ##############################################
538
727
 
539
- def get_first(data, test = True): # if test take the first element, otherwise the second
728
+
729
+ def get_first(data, test=True): # if test take the first element, otherwise the second
540
730
  return data[0] if test else data[1]
541
731
 
542
- def apply_scale(data, test = False): # apply log scale if test
732
+
733
+ def apply_scale(data, test=False): # apply log scale if test
543
734
  return log(data) if test else data
544
735
 
545
- def reverse_scale(data, test = False): # apply log scale if test
736
+
737
+ def reverse_scale(data, test=False): # apply log scale if test
546
738
  return power10(data) if test else data
547
739
 
548
- def replace_none(data, num_data): # replace None elements in data with correspondent in num_data
740
+
741
+ def replace_none(
742
+ data, num_data
743
+ ): # replace None elements in data with correspondent in num_data
549
744
  return [data[i] if data[i] is not None else num_data[i] for i in range(len(data))]
550
745
 
551
- numerical = lambda el: not (el is None or math.isnan(el)) or isinstance(el, str) # in the case of string datetimes
552
- all_numerical = lambda data: all([numerical(el) for el in data])
553
746
 
554
- def get_lim(data): # it returns the data minimum and maximum limits
747
+ def numerical(el):
748
+ return not (el is None or math.isnan(el)) or isinstance(
749
+ el, str
750
+ ) # in the case of string datetimes
751
+ def all_numerical(data):
752
+ return all(numerical(el) for el in data)
753
+
754
+
755
+ def get_lim(data): # it returns the data minimum and maximum limits
555
756
  data = [el for el in data if numerical(el)]
556
- m = min(data, default = 0)
557
- M = max(data, default = 0)
558
- m, M = (m, M) if m != M else (0.5 * m, 1.5 * m) if m == M != 0 else (-1, 1)
559
- return [m, M]
757
+ m = min(data, default=0)
758
+ max_val = max(data, default=0)
759
+ m, max_val = (m, max_val) if m != max_val else (0.5 * m, 1.5 * m) if m == max_val != 0 else (-1, 1)
760
+ return [m, max_val]
761
+
560
762
 
561
- def get_matrix_data(data, lim, bins): # from data to relative canvas coordinates
562
- change = lambda el: 0.5 + (bins - 1) * (el - lim[0]) / (lim[1] - lim[0])
763
+ def get_matrix_data(data, lim, bins): # from data to relative canvas coordinates
764
+ def change(el):
765
+ return 0.5 + (bins - 1) * (el - lim[0]) / (lim[1] - lim[0])
563
766
  # round is so that for example 9.9999 = 10, otherwise the floor function will give different results
564
767
  return [math.floor(round(change(el), 8)) if numerical(el) else el for el in data]
565
768
 
566
- 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
769
+
770
+ def get_lines(
771
+ x, y, *other
772
+ ): # 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
567
773
  # if len(x) * len(y) == 0:
568
774
  # return [], [], *[[]] * len(other)
569
775
  o = transpose(other, len(other))
@@ -579,7 +785,10 @@ def get_lines(x, y, *other): # it returns the lines between all couples of data
579
785
  ol = ol + [o[-1]] if x != [] else ol
580
786
  return [xl, yl] + transpose(ol, len(other))
581
787
 
582
- 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.
788
+
789
+ def get_line(
790
+ x, y
791
+ ): # 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.
583
792
  if not all_numerical(join([x, y])):
584
793
  return x, y
585
794
  x0, x1 = x
@@ -591,6 +800,7 @@ def get_line(x, y): # it returns a line of points from x[0],y[0] to x[1],y[1] di
591
800
  y = [int(el) for el in linspace(y0, y1, a)]
592
801
  return [x, y]
593
802
 
803
+
594
804
  def get_fill_level(fill, lim, bins):
595
805
  if fill is False:
596
806
  return False
@@ -601,37 +811,42 @@ def get_fill_level(fill, lim, bins):
601
811
  fill = get_matrix_data([fill], lim, bins)[0]
602
812
  return fill
603
813
 
814
+
604
815
  def find_filling_values(x, y, y0):
605
816
  xn, yn, yf = [[]] * 3
606
- l = len(x);
817
+ x_length = len(x)
607
818
  while len(x) > 0:
608
819
  i = len(xn)
609
820
  xn.append(x[i])
610
821
  yn.append(y[i])
611
- J = [j for j in range(l) if x[j] == x[i]]
612
- if J != []:
613
- Y = [y[j] for j in J]
614
- j = Y.index(min(Y))
615
- J.pop(j)
616
- [x.pop(j) for j in J]
617
- [y.pop(j) for j in J]
822
+ indices = [j for j in range(x_length) if x[j] == x[i]]
823
+ if indices != []:
824
+ y_subset = [y[j] for j in indices]
825
+ j = y_subset.index(min(y_subset))
826
+ indices.pop(j)
827
+ [x.pop(j) for j in indices]
828
+ [y.pop(j) for j in indices]
618
829
  yf.append(y[j])
619
830
  return xn, yn, yf
620
831
 
832
+
621
833
  def get_fill_boundaries(x, y):
622
834
  xm = []
623
- l = len(x)
624
- for i in range(l):
835
+ x_length = len(x)
836
+ for i in range(x_length):
625
837
  xi, yi = x[i], y[i]
626
- I = [j for j in range(l) if x[j] == xi and y[j] < yi]
627
- Y = [y[j] for j in I]
628
- m = min(Y, default = yi)
838
+ indices = [j for j in range(x_length) if x[j] == xi and y[j] < yi]
839
+ y_values = [y[j] for j in indices]
840
+ m = min(y_values, default=yi)
629
841
  xm.append([x[i], m])
630
842
  x, m = transpose(xm)
631
843
  return m
632
844
 
633
- 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
634
- #y0 = get_fill_boundaries(x, y)
845
+
846
+ def fill_data(
847
+ x, y, y0, *other
848
+ ): # it fills x, y with y data points reaching y0; and c are the list of markers and colors that needs to be elongated
849
+ # y0 = get_fill_boundaries(x, y)
635
850
  y0 = get_fill_boundaries(x, y) if isinstance(y0, str) else [y0] * len(x)
636
851
  o = transpose(other, len(other))
637
852
  xf, yf, of = [[] for i in range(3)]
@@ -640,7 +855,11 @@ def fill_data(x, y, y0, *other): # it fills x, y with y data points reaching y0;
640
855
  xi, yi, y0i = x[i], y[i], y0[i]
641
856
  if [xi, yi] not in xy:
642
857
  xy.append([xi, yi])
643
- yn = range(y0i, yi + 1) if y0i < yi else range(yi, y0i) if y0i > yi else [y0i]
858
+ yn = (
859
+ range(y0i, yi + 1)
860
+ if y0i < yi
861
+ else range(yi, y0i) if y0i > yi else [y0i]
862
+ )
644
863
  yn = list(yn)
645
864
  xn = [xi] * len(yn)
646
865
  xf += xn
@@ -648,43 +867,59 @@ def fill_data(x, y, y0, *other): # it fills x, y with y data points reaching y0;
648
867
  of += [o[i]] * len(xn)
649
868
  return [xf, yf] + transpose(of, len(other))
650
869
 
870
+
651
871
  def remove_outsiders(x, y, width, height, *other):
652
- I = [i for i in range(len(x)) if x[i] in range(width) and y[i] in range(height)]
872
+ indices = [i for i in range(len(x)) if x[i] in range(width) and y[i] in range(height)]
653
873
  o = transpose(other, len(other))
654
- return transpose([(x[i], y[i], *o[i]) for i in I], 2 + len(other))
874
+ return transpose([(x[i], y[i], *o[i]) for i in indices], 2 + len(other))
655
875
 
656
- def get_labels(ticks): # it returns the approximated string version of the data ticks
876
+
877
+ def get_labels(ticks): # it returns the approximated string version of the data ticks
657
878
  d = distinguishing_digit(ticks)
658
879
  formatting_string = "{:." + str(d + 1) + "f}"
659
880
  labels = [formatting_string.format(el) for el in ticks]
660
- pos = [el.index('.') + d + 2 for el in labels]
881
+ pos = [el.index(".") + d + 2 for el in labels]
661
882
  labels = [labels[i][: pos[i]] for i in range(len(labels))]
662
- all_integers = all(map(lambda el: el == int(el), ticks))
663
- 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]
664
- #sign = any([el < 0 for el in ticks])
665
- #labels = ['+' + labels[i] if ticks[i] > 0 and sign else labels[i] for i in range(len(labels))]
883
+ all_integers = all(el == int(el) for el in ticks)
884
+ labels = (
885
+ [add_extra_zeros(el, d) if len(labels) > 1 else el for el in labels]
886
+ if not all_integers
887
+ else [str(int(el)) for el in ticks]
888
+ )
889
+ # sign = any([el < 0 for el in ticks])
890
+ # labels = ['+' + labels[i] if ticks[i] > 0 and sign else labels[i] for i in range(len(labels))]
666
891
  return labels
667
892
 
668
- def distinguishing_digit(data): # it return the minimum amount of decimal digits necessary to distinguish all elements of a list
669
- #data = [el for el in data if 'e' not in str(el)]
893
+
894
+ def distinguishing_digit(
895
+ data,
896
+ ): # it return the minimum amount of decimal digits necessary to distinguish all elements of a list
897
+ # data = [el for el in data if 'e' not in str(el)]
670
898
  d = [_distinguishing_digit(data[i], data[i + 1]) for i in range(len(data) - 1)]
671
- return max(d, default = 1)
899
+ return max(d, default=1)
672
900
 
673
- 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).
901
+
902
+ def _distinguishing_digit(
903
+ a, b
904
+ ): # it return the minimum amount of decimal digits necessary to distinguish a from b (when both are rounded to those digits).
674
905
  d = abs(a - b)
675
- d = 0 if d == 0 else - math.log10(2 * d)
676
- #d = round(d, 10)
906
+ d = 0 if d == 0 else -math.log10(2 * d)
907
+ # d = round(d, 10)
677
908
  d = 0 if d < 0 else math.ceil(d)
678
909
  d = d + 1 if round(a, d) == round(b, d) else d
679
910
  return d
680
911
 
681
- def add_extra_zeros(label, d): # it adds 0s at the end of a label if necessary
682
- zeros = len(label) - 1 - label.index('.' if 'e' not in label else 'e')
912
+
913
+ def add_extra_zeros(label, d): # it adds 0s at the end of a label if necessary
914
+ zeros = len(label) - 1 - label.index("." if "e" not in label else "e")
683
915
  if zeros < d:
684
- label += '0' * (d - zeros)
916
+ label += "0" * (d - zeros)
685
917
  return label
686
918
 
687
- def add_extra_spaces(labels, side): # it adds empty spaces before or after the labels if necessary
919
+
920
+ def add_extra_spaces(
921
+ labels, side
922
+ ): # it adds empty spaces before or after the labels if necessary
688
923
  length = 0 if labels == [] else max_length(labels)
689
924
  if side == "left":
690
925
  labels = [space * (length - len(el)) + el for el in labels]
@@ -692,17 +927,20 @@ def add_extra_spaces(labels, side): # it adds empty spaces before or after the l
692
927
  labels = [el + space * (length - len(el)) for el in labels]
693
928
  return labels
694
929
 
695
- def hd_group(x, y, xf, yf): # it returns the real coordinates of the HD markers and the matrix that defines the marker
696
- l, xfm, yfm = len(x), max(xf), max(yf)
930
+
931
+ def hd_group(
932
+ x, y, xf, yf
933
+ ): # it returns the real coordinates of the HD markers and the matrix that defines the marker
934
+ x_length, xfm, yfm = len(x), max(xf), max(yf)
697
935
  xm = [el // xfm if numerical(el) else el for el in x]
698
936
  ym = [el // yfm if numerical(el) else el for el in y]
699
937
  m = {}
700
- for i in range(l):
938
+ for i in range(x_length):
701
939
  xyi = xm[i], ym[i]
702
940
  xfi, yfi = xf[i], yf[i]
703
941
  mi = [[0 for x in range(xfi)] for y in range(yfi)]
704
942
  m[xyi] = mi
705
- for i in range(l):
943
+ for i in range(x_length):
706
944
  xyi = xm[i], ym[i]
707
945
  if all_numerical(xyi):
708
946
  xk, yk = x[i] % xfi, y[i] % yfi
@@ -712,42 +950,50 @@ def hd_group(x, y, xf, yf): # it returns the real coordinates of the HD markers
712
950
  m = [tuple(join(el[::-1])) for el in m.values()]
713
951
  return x, y, m
714
952
 
953
+
715
954
  ###############################################
716
955
  ############# Bar Functions ##############
717
956
  ###############################################
718
957
 
719
- def bars(x, y, width, minimum): # given the bars center coordinates and height, it returns the full bar coordinates
720
- # if x == []:
721
- # return [], []
722
- bins = len(x)
723
- #bin_size_half = (max(x) - min(x)) / (bins - 1) * width / 2
724
- bin_size_half = width / 2
725
- # adjust the bar width according to the number of bins
726
- if bins > 1:
727
- bin_size_half *= (max(x) - min(x)) / (bins - 1)
728
- xbar, ybar = [], []
729
- for i in range(bins):
730
- xbar.append([x[i] - bin_size_half, x[i] + bin_size_half])
731
- ybar.append([minimum, y[i]])
732
- return xbar, ybar
958
+
959
+ def bars(
960
+ x, y, width, minimum
961
+ ): # given the bars center coordinates and height, it returns the full bar coordinates
962
+ # if x == []:
963
+ # return [], []
964
+ bins = len(x)
965
+ # bin_size_half = (max(x) - min(x)) / (bins - 1) * width / 2
966
+ bin_size_half = width / 2
967
+ # adjust the bar width according to the number of bins
968
+ if bins > 1:
969
+ bin_size_half *= (max(x) - min(x)) / (bins - 1)
970
+ xbar, ybar = [], []
971
+ for i in range(bins):
972
+ xbar.append([x[i] - bin_size_half, x[i] + bin_size_half])
973
+ ybar.append([minimum, y[i]])
974
+ return xbar, ybar
975
+
733
976
 
734
977
  def set_multiple_bar_data(*args):
735
- l = len(args)
736
- Y = [] if l == 0 else args[0] if l == 1 else args[1]
737
- Y = [Y] if not isinstance(Y, list) or len(Y) == 0 else Y
738
- m = len(Y[0])
739
- x = [] if l == 0 else list(range(1, m + 1)) if l == 1 else args[0]
740
- return x, Y
741
-
742
- 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.
743
- #data = [round(el, 15) for el in data]
978
+ arg_count = len(args)
979
+ y_values = [] if arg_count == 0 else args[0] if arg_count == 1 else args[1]
980
+ y_values = [y_values] if not isinstance(y_values, list) or len(y_values) == 0 else y_values
981
+ m = len(y_values[0])
982
+ x = [] if arg_count == 0 else list(range(1, m + 1)) if arg_count == 1 else args[0]
983
+ return x, y_values
984
+
985
+
986
+ def hist_data(
987
+ data, bins=10, norm=False
988
+ ): # it returns data in histogram form if norm is False. Otherwise, it returns data in density form where all bins sum to 1.
989
+ # data = [round(el, 15) for el in data]
744
990
  # if data == []:
745
991
  # return [], []
746
992
  bins = 0 if len(data) == 0 else bins
747
- m, M = min(data, default = 0), max(data, default = 0)
748
- data = [(el - m) / (M - m) * bins if el != M else bins - 1 for el in data]
993
+ m, max_val = min(data, default=0), max(data, default=0)
994
+ data = [(el - m) / (max_val - m) * bins if el != max_val else bins - 1 for el in data]
749
995
  data = [int(el) for el in data]
750
- histx = linspace(m, M, bins)
996
+ histx = linspace(m, max_val, bins)
751
997
  histy = [0] * bins
752
998
  for el in data:
753
999
  histy[el] += 1
@@ -755,22 +1001,24 @@ def hist_data(data, bins = 10, norm = False): # it returns data in histogram for
755
1001
  histy = [el / len(data) for el in histy]
756
1002
  return histx, histy
757
1003
 
1004
+
758
1005
  def single_bar(x, y, ylabel, marker, colors):
759
- l = len(y)
1006
+ y_length = len(y)
760
1007
  lc = len(colors)
761
- xs = colorize(str(x), 'gray+', 'bold')
1008
+ xs = colorize(str(x), "gray+", "bold")
762
1009
  bar = [marker * el for el in y]
763
- bar = [apply_ansi(bar[i], colors[i % lc], 1) for i in range(l)]
764
- ylabel = colorize(f'{ylabel:.2f}', 'gray+', 'bold')
765
- bar = xs + space + ''.join(bar) + space + ylabel
1010
+ bar = [apply_ansi(bar[i], colors[i % lc], 1) for i in range(y_length)]
1011
+ ylabel = colorize(f"{ylabel:.2f}", "gray+", "bold")
1012
+ bar = xs + space + "".join(bar) + space + ylabel
766
1013
  return bar
767
1014
 
768
- def bar_data(*args, width = None, mode = 'stacked'):
769
- x, Y = set_multiple_bar_data(*args)
1015
+
1016
+ def bar_data(*args, width=None, mode="stacked"):
1017
+ x, y_values = set_multiple_bar_data(*args)
770
1018
  x = list(map(str, x))
771
- x = add_extra_spaces(x, 'right')
1019
+ x = add_extra_spaces(x, "right")
772
1020
  lx = len(x[0])
773
- y = [sum(el) for el in transpose(Y)] if mode == 'stacked' else Y
1021
+ y = [sum(el) for el in transpose(y_values)] if mode == "stacked" else y_values
774
1022
  ly = max_length([round(el, 2) for el in join(y)])
775
1023
 
776
1024
  width_term = terminal_width()
@@ -780,48 +1028,63 @@ def bar_data(*args, width = None, mode = 'stacked'):
780
1028
  my = max(join(y))
781
1029
  my = 1 if my == 0 else my
782
1030
  dx = my / (width - lx - ly - 2)
783
- Yi = [[round(el / dx, 0) for el in y] for y in Y]
784
- Yi = transpose(Yi)
1031
+ y_scaled = [[round(el / dx, 0) for el in y] for y in y_values]
1032
+ y_scaled = transpose(y_scaled)
785
1033
 
786
- return x, y, Yi, width
1034
+ return x, y, y_scaled, width
787
1035
 
788
- def correct_marker(marker = None):
1036
+
1037
+ def correct_marker(marker=None):
789
1038
  return simple_bar_marker if marker is None else marker[0]
790
1039
 
1040
+
791
1041
  def get_title(title, width):
792
- out = ''
1042
+ out = ""
793
1043
  if title is not None:
794
- l = len(uncolorize(title))
795
- w1 = (width - 2 - l) // 2; w2 = width - l - 2 - w1
796
- l1 = '─' * w1 + space
797
- l2 = space + '' * w2
798
- out = colorize(l1 + title + l2, 'gray+', 'bold') + '\n'
1044
+ title_length = len(uncolorize(title))
1045
+ w1 = (width - 2 - title_length) // 2
1046
+ w2 = width - title_length - 2 - w1
1047
+ l1 = "" * w1 + space
1048
+ l2 = space + "─" * w2
1049
+ out = colorize(l1 + title + l2, "gray+", "bold") + "\n"
799
1050
  return out
800
1051
 
1052
+
801
1053
  def get_simple_labels(marker, labels, colors, width):
802
- out = '\n'
803
- if labels != None:
804
- l = len(labels)
1054
+ out = "\n"
1055
+ if labels is not None:
1056
+ label_count = len(labels)
805
1057
  lc = len(colors)
806
- out = space.join([colorize(marker * 3, colors[i % lc]) + space + colorize(labels[i], 'gray+', 'bold') for i in range(l)])
807
- out = '\n' + get_title(out, width)
1058
+ out = space.join(
1059
+ [
1060
+ colorize(marker * 3, colors[i % lc])
1061
+ + space
1062
+ + colorize(labels[i], "gray+", "bold")
1063
+ for i in range(label_count)
1064
+ ]
1065
+ )
1066
+ out = "\n" + get_title(out, width)
808
1067
  return out
809
1068
 
1069
+
810
1070
  ###############################################
811
1071
  ############# Box Functions ##############
812
1072
  ###############################################
813
1073
 
814
- def box(x, y, width, minimum): # given the bars center coordinates and height, it returns the full bar coordinates
1074
+
1075
+ def box(
1076
+ x, y, width, minimum
1077
+ ): # given the bars center coordinates and height, it returns the full bar coordinates
815
1078
  # if x == []:
816
1079
  # return [], []
817
1080
  bins = len(x)
818
- #bin_size_half = (max(x) - min(x)) / (bins - 1) * width / 2
1081
+ # bin_size_half = (max(x) - min(x)) / (bins - 1) * width / 2
819
1082
  bin_size_half = width / 2
820
1083
  # adjust the bar width according to the number of bins
821
1084
  if bins > 1:
822
1085
  bin_size_half *= (max(x) - min(x)) / (bins - 1)
823
- c, q1, q2, q3, h, l = [], [], [], [], [], []
824
- xbar, ybar, mybar = [], [], []
1086
+ c, q1, q2, q3, h, low_vals = [], [], [], [], [], []
1087
+ xbar, _ybar, _mybar = [], [], []
825
1088
 
826
1089
  for i in range(bins):
827
1090
  c.append(x[i])
@@ -830,24 +1093,29 @@ def box(x, y, width, minimum): # given the bars center coordinates and height, i
830
1093
  q2.append(quantile(y[i], 0.50))
831
1094
  q3.append(quantile(y[i], 0.75))
832
1095
  h.append(max(y[i]))
833
- l.append(min(y[i]))
1096
+ low_vals.append(min(y[i]))
1097
+
1098
+ return q1, q2, q3, h, low_vals, c, xbar
834
1099
 
835
- return q1, q2, q3, h, l, c, xbar
836
1100
 
837
1101
  ##############################################
838
1102
  ########## Image Utilities #############
839
1103
  ##############################################
840
1104
 
841
- 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
1105
+
1106
+ def update_size(
1107
+ size_old, size_new
1108
+ ): # 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
842
1109
  size_old = [size_old[0], size_old[1] / 2]
843
- ratio_old = size_old[1] / size_old[0]
1110
+ size_old[1] / size_old[0]
844
1111
  size_new = replace(size_new, size_old)
845
- ratio_new = size_new[1] / size_new[0]
846
- #ratio_new = size_new[1] / size_new[0]
1112
+ size_new[1] / size_new[0]
1113
+ # ratio_new = size_new[1] / size_new[0]
847
1114
  size_new = [1 if el == 0 else el for el in size_new]
848
1115
  return [int(size_new[0]), int(size_new[1])]
849
1116
 
850
- def image_to_matrix(image): # from image to a matrix of pixels
1117
+
1118
+ def image_to_matrix(image): # from image to a matrix of pixels
851
1119
  pixels = list(image.getdata())
852
1120
  width, height = image.size
853
- return [pixels[i * width:(i + 1) * width] for i in range(height)]
1121
+ return [pixels[i * width : (i + 1) * width] for i in range(height)]