plotext-plus 1.0.1__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/__init__.py +31 -0
- plotext_plus/__main__.py +2 -0
- plotext_plus/_api.py +828 -0
- plotext_plus/_build.py +270 -0
- plotext_plus/_core.py +581 -0
- plotext_plus/_date.py +60 -0
- plotext_plus/_default.py +83 -0
- plotext_plus/_dict.py +210 -0
- plotext_plus/_doc.py +707 -0
- plotext_plus/_doc_utils.py +291 -0
- plotext_plus/_figure.py +488 -0
- plotext_plus/_global.py +370 -0
- plotext_plus/_matrix.py +171 -0
- plotext_plus/_monitor.py +848 -0
- plotext_plus/_output.py +136 -0
- plotext_plus/_shtab.py +9 -0
- plotext_plus/_themes.py +343 -0
- plotext_plus/_utility.py +853 -0
- plotext_plus/api.py +828 -0
- plotext_plus/charts.py +42 -0
- plotext_plus/core.py +581 -0
- plotext_plus/mcp_cli.py +79 -0
- plotext_plus/mcp_server.py +505 -0
- plotext_plus/plotext_cli.py +375 -0
- plotext_plus/plotting.py +92 -0
- plotext_plus/themes.py +29 -0
- plotext_plus/utilities.py +52 -0
- plotext_plus/utils.py +370 -0
- plotext_plus-1.0.1.dist-info/METADATA +303 -0
- plotext_plus-1.0.1.dist-info/RECORD +33 -0
- plotext_plus-1.0.1.dist-info/WHEEL +4 -0
- plotext_plus-1.0.1.dist-info/entry_points.txt +3 -0
- plotext_plus-1.0.1.dist-info/licenses/LICENSE +23 -0
plotext_plus/_utility.py
ADDED
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import sys, shutil, os, re, math, inspect
|
|
2
|
+
from plotext_plus._dict import *
|
|
3
|
+
|
|
4
|
+
###############################################
|
|
5
|
+
######### Number Manipulation ##########
|
|
6
|
+
###############################################
|
|
7
|
+
|
|
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
|
|
10
|
+
f = math.floor(n)
|
|
11
|
+
r = f if n - f < 0.5 else math.ceil(n)
|
|
12
|
+
return r * 10 ** (-d)
|
|
13
|
+
|
|
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
|
+
|
|
17
|
+
def 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
|
+
|
|
24
|
+
def try_float(data): # it turn a string into float if it can
|
|
25
|
+
try:
|
|
26
|
+
return float(data)
|
|
27
|
+
except:
|
|
28
|
+
return data
|
|
29
|
+
|
|
30
|
+
def 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
|
+
|
|
42
|
+
def 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
|
+
|
|
46
|
+
def 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
|
+
|
|
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
|
|
55
|
+
return [step(i) for i in range(length)]
|
|
56
|
+
|
|
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]
|
|
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
|
+
|
|
62
|
+
def 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
|
+
|
|
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
|
+
|
|
72
|
+
def power10(data): # it apply log function to the data
|
|
73
|
+
return [10 ** el for el in data]
|
|
74
|
+
|
|
75
|
+
def floor(data): # it floors a list of data
|
|
76
|
+
return list(map(math.floor, data))
|
|
77
|
+
|
|
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]
|
|
82
|
+
|
|
83
|
+
###############################################
|
|
84
|
+
########## List Manipulation ###########
|
|
85
|
+
###############################################
|
|
86
|
+
|
|
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
|
+
|
|
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])]
|
|
94
|
+
|
|
95
|
+
def 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
|
+
|
|
105
|
+
def matrix_size(matrix): # cols, height
|
|
106
|
+
return [len(matrix[0]), len(matrix)] if matrix != [] else [0, 0]
|
|
107
|
+
|
|
108
|
+
def transpose(data, length = 1): # it needs no explanation
|
|
109
|
+
return [[]] * length if data == [] else list(map(list, zip(*data)))
|
|
110
|
+
|
|
111
|
+
def vstack(matrix, extra): # vertical stack of two matrices
|
|
112
|
+
return extra + matrix # + extra
|
|
113
|
+
|
|
114
|
+
def 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
|
+
|
|
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))
|
|
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
|
+
|
|
137
|
+
nl = "\n"
|
|
138
|
+
|
|
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
|
+
|
|
142
|
+
def 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
|
+
|
|
152
|
+
positive_color = 'green+'
|
|
153
|
+
negative_color = 'red'
|
|
154
|
+
title_color = 'cyan+'
|
|
155
|
+
|
|
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
|
+
|
|
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)
|
|
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
|
+
|
|
167
|
+
def 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
|
+
|
|
172
|
+
def 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
|
+
|
|
187
|
+
def 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
|
+
|
|
192
|
+
def max_length(strings):
|
|
193
|
+
strings = map(str, strings)
|
|
194
|
+
return max(map(len, strings), default = 0)
|
|
195
|
+
|
|
196
|
+
###############################################
|
|
197
|
+
########## File Manipulation ############
|
|
198
|
+
###############################################
|
|
199
|
+
|
|
200
|
+
def 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
|
+
|
|
206
|
+
def 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
|
+
|
|
211
|
+
def script_folder(): # the folder of the script executed
|
|
212
|
+
return parent_folder(inspect.getfile(sys._getframe(1)))
|
|
213
|
+
|
|
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
|
|
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
|
+
|
|
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
|
|
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
|
+
|
|
227
|
+
def 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
|
+
|
|
233
|
+
def 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
|
+
|
|
242
|
+
def 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
|
+
|
|
253
|
+
def 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
|
+
|
|
260
|
+
def 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
|
+
|
|
270
|
+
def is_ipython(): # true if running in ipython shenn
|
|
271
|
+
try:
|
|
272
|
+
__IPYTHON__
|
|
273
|
+
return True
|
|
274
|
+
except NameError:
|
|
275
|
+
return False
|
|
276
|
+
|
|
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'
|
|
283
|
+
|
|
284
|
+
platform = platform()
|
|
285
|
+
|
|
286
|
+
# to enable ascii escape color sequences
|
|
287
|
+
if platform == "windows":
|
|
288
|
+
import subprocess
|
|
289
|
+
subprocess.call('', shell = True)
|
|
290
|
+
|
|
291
|
+
def 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
|
+
|
|
298
|
+
def terminal_width(): # returns terminal width, adjusted for banner borders when banners are enabled
|
|
299
|
+
width = terminal_size()[0]
|
|
300
|
+
if width is None:
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
# Check if banners are enabled by checking the global output instance
|
|
304
|
+
try:
|
|
305
|
+
from plotext_plus._output import get_output_instance
|
|
306
|
+
output_instance = get_output_instance()
|
|
307
|
+
if hasattr(output_instance, 'use_banners') and output_instance.use_banners:
|
|
308
|
+
# Rich Panel with borders and padding=(0, 1) uses:
|
|
309
|
+
# - 2 characters for left/right borders
|
|
310
|
+
# - 2 characters for padding (1 on each side)
|
|
311
|
+
# Total: 4 characters of horizontal space
|
|
312
|
+
return max(width - 4, 1) # Ensure minimum width of 1
|
|
313
|
+
except:
|
|
314
|
+
pass # If anything fails, fall back to original width
|
|
315
|
+
|
|
316
|
+
return width
|
|
317
|
+
|
|
318
|
+
tw = terminal_width
|
|
319
|
+
|
|
320
|
+
terminal_height = lambda: terminal_size()[1]
|
|
321
|
+
th = terminal_height
|
|
322
|
+
|
|
323
|
+
def clear_terminal(lines = None): # it cleat the entire terminal, or the specified number of lines
|
|
324
|
+
if lines is None:
|
|
325
|
+
write('\033c')
|
|
326
|
+
else:
|
|
327
|
+
for r in range(lines):
|
|
328
|
+
write("\033[A") # moves the curson up
|
|
329
|
+
write("\033[2K") # clear the entire line
|
|
330
|
+
|
|
331
|
+
def write(string): # the print function used by plotext - now uses chuk-term backend
|
|
332
|
+
from plotext_plus._output import write as output_write
|
|
333
|
+
output_write(string)
|
|
334
|
+
|
|
335
|
+
class memorize: # it memorise the arguments of a function, when used as its decorator, to reduce computational time
|
|
336
|
+
def __init__(self, f):
|
|
337
|
+
self.f = f
|
|
338
|
+
self.memo = {}
|
|
339
|
+
def __call__(self, *args):
|
|
340
|
+
if not args in self.memo:
|
|
341
|
+
self.memo[args] = self.f(*args)
|
|
342
|
+
return self.memo[args]
|
|
343
|
+
|
|
344
|
+
##############################################
|
|
345
|
+
######### Marker Utilities ###########
|
|
346
|
+
##############################################
|
|
347
|
+
|
|
348
|
+
space = ' ' # the default null character that appears as background to all plots
|
|
349
|
+
plot_marker = "hd" if platform == 'unix' else 'dot'
|
|
350
|
+
|
|
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 = '▇'
|
|
355
|
+
|
|
356
|
+
@memorize
|
|
357
|
+
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]
|
|
359
|
+
|
|
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
|
+
|
|
363
|
+
##############################################
|
|
364
|
+
########### Color Utilities ############
|
|
365
|
+
##############################################
|
|
366
|
+
|
|
367
|
+
# 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
|
|
371
|
+
|
|
372
|
+
# Along side the user needs to specify whatever it is for background / fullground / style
|
|
373
|
+
# which plotext calls 'character' = 0 / 1 / 2
|
|
374
|
+
|
|
375
|
+
|
|
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]
|
|
377
|
+
|
|
378
|
+
def get_color_code(color): # the color number code from color string
|
|
379
|
+
color = color.strip()
|
|
380
|
+
return color_codes[color]
|
|
381
|
+
|
|
382
|
+
def get_color_name(code): # the color string from color number code
|
|
383
|
+
codes = list(color_codes.values())
|
|
384
|
+
return colors[codes.index(code)] if code in codes else no_color
|
|
385
|
+
|
|
386
|
+
def is_string_color(color):
|
|
387
|
+
return isinstance(color, str) and color.strip() in colors
|
|
388
|
+
|
|
389
|
+
def is_integer_color(color):
|
|
390
|
+
return isinstance(color, int) and 0 <= color <= 255
|
|
391
|
+
|
|
392
|
+
def is_rgb_color(color):
|
|
393
|
+
is_rgb = isinstance(color, list) or isinstance(color, tuple)
|
|
394
|
+
is_rgb = is_rgb and len(color) == 3
|
|
395
|
+
is_rgb = is_rgb and all([is_integer_color(el) for el in color])
|
|
396
|
+
return is_rgb
|
|
397
|
+
|
|
398
|
+
def is_color(color):
|
|
399
|
+
return is_string_color(color) or is_integer_color(color) or is_rgb_color(color)
|
|
400
|
+
|
|
401
|
+
def colorize(string, color = None, style = None, background = None, show = False): # it paints a text with given fullground and background color
|
|
402
|
+
string = apply_ansi(string, background, 0)
|
|
403
|
+
string = apply_ansi(string, color, 1)
|
|
404
|
+
string = apply_ansi(string, style, 2)
|
|
405
|
+
if show:
|
|
406
|
+
print(string)
|
|
407
|
+
return string
|
|
408
|
+
|
|
409
|
+
def uncolorize(string): # remove color codes from colored string
|
|
410
|
+
colored = lambda: ansi_begin in string
|
|
411
|
+
while colored():
|
|
412
|
+
b = string.index(ansi_begin)
|
|
413
|
+
e = string[b : ].index('m') + b + 1
|
|
414
|
+
string = string.replace(string[b : e], '')
|
|
415
|
+
return string
|
|
416
|
+
|
|
417
|
+
def apply_ansi(string, color, character):
|
|
418
|
+
begin, end = ansi(color, character)
|
|
419
|
+
return begin + string + end
|
|
420
|
+
|
|
421
|
+
#ansi_begin = '\033['
|
|
422
|
+
ansi_begin = '\x1b['
|
|
423
|
+
ansi_end = ansi_begin + '0m'
|
|
424
|
+
|
|
425
|
+
@memorize
|
|
426
|
+
def colors_to_ansi(fullground, style, background):
|
|
427
|
+
color = [background, fullground, style]
|
|
428
|
+
return ''.join([ansi(color[i], i)[0] for i in range(3)])
|
|
429
|
+
|
|
430
|
+
@memorize
|
|
431
|
+
def ansi(color, character):
|
|
432
|
+
if color == no_color:
|
|
433
|
+
return ['', '']
|
|
434
|
+
col, fg, tp = '', '', ''
|
|
435
|
+
if character == 2 and is_style(color):
|
|
436
|
+
col = get_style_codes(color)
|
|
437
|
+
col = ';'.join([str(el) for el in col])
|
|
438
|
+
elif character != 2:
|
|
439
|
+
fg = '38;' if character == 1 else '48;'
|
|
440
|
+
tp = '5;'
|
|
441
|
+
if is_string_color(color):
|
|
442
|
+
col = str(get_color_code(color))
|
|
443
|
+
elif is_integer_color(color):
|
|
444
|
+
col = str(color)
|
|
445
|
+
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 ''
|
|
451
|
+
return [begin, end]
|
|
452
|
+
|
|
453
|
+
## 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
|
+
|
|
455
|
+
def to_rgb(color):
|
|
456
|
+
if is_string_color(color): # from 0 to 1
|
|
457
|
+
color = get_color_code(color)
|
|
458
|
+
#color = type0_to_type1_codes[code]
|
|
459
|
+
if is_integer_color(color): # from 0 or 1 to 2
|
|
460
|
+
return type1_to_type2_codes[color]
|
|
461
|
+
return color
|
|
462
|
+
|
|
463
|
+
##############################################
|
|
464
|
+
############ Style Codes ##############
|
|
465
|
+
##############################################
|
|
466
|
+
|
|
467
|
+
no_style = 'default'
|
|
468
|
+
|
|
469
|
+
styles = list(style_codes.keys()) + [no_style]
|
|
470
|
+
|
|
471
|
+
info_style = 'dim'
|
|
472
|
+
|
|
473
|
+
def get_style_code(style): # from single style to style number code
|
|
474
|
+
style = style.strip()
|
|
475
|
+
return style_codes[style]
|
|
476
|
+
|
|
477
|
+
def get_style_codes(style): # from many styles (separated by space) to as many number codes
|
|
478
|
+
style = style.strip().split()
|
|
479
|
+
codes = [get_style_code(el) for el in style if el in styles]
|
|
480
|
+
codes = no_duplicates(codes)
|
|
481
|
+
return codes
|
|
482
|
+
|
|
483
|
+
def get_style_name(code): # from style number code to style name
|
|
484
|
+
codes = list(style_codes.values())
|
|
485
|
+
return styles[codes.index(code)] if code in codes else no_style
|
|
486
|
+
|
|
487
|
+
def clean_styles(style): # it returns a well written sequence of styles (separated by spaces) from a possible confused one
|
|
488
|
+
codes = get_style_codes(style)
|
|
489
|
+
return ' '.join([get_style_name(el) for el in codes])
|
|
490
|
+
|
|
491
|
+
def is_style(style):
|
|
492
|
+
style = style.strip().split() if isinstance(style, str) else ['']
|
|
493
|
+
return any([el in styles for el in style])
|
|
494
|
+
|
|
495
|
+
##############################################
|
|
496
|
+
########### Plot Utilities ############
|
|
497
|
+
##############################################
|
|
498
|
+
|
|
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)]
|
|
511
|
+
|
|
512
|
+
##############################################
|
|
513
|
+
####### Figure Class Utilities ########
|
|
514
|
+
##############################################
|
|
515
|
+
|
|
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
|
|
517
|
+
bins = len(sizes)
|
|
518
|
+
for s in range(bins):
|
|
519
|
+
size_set = sum([el for el in sizes[0 : s] + sizes[s + 1 : ] if el is not None])
|
|
520
|
+
available = max(size_max - size_set, 0)
|
|
521
|
+
to_set = len([el for el in sizes[s : ] if el is None])
|
|
522
|
+
sizes[s] = available // to_set if sizes[s] is None else sizes[s]
|
|
523
|
+
return sizes
|
|
524
|
+
|
|
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
|
|
526
|
+
bins = len(sizes)
|
|
527
|
+
s = bins - 1
|
|
528
|
+
#while (sum(sizes) != size_max if not_less else sum(sizes) > size_max) and s >= 0:
|
|
529
|
+
while sum(sizes) > size_max and s >= 0:
|
|
530
|
+
other_sizes = sum([sizes[i] for i in range(bins) if i != s])
|
|
531
|
+
sizes[s] = max(size_max - other_sizes, 0)
|
|
532
|
+
s -= 1
|
|
533
|
+
return sizes
|
|
534
|
+
|
|
535
|
+
##############################################
|
|
536
|
+
####### Build Class Utilities #########
|
|
537
|
+
##############################################
|
|
538
|
+
|
|
539
|
+
def get_first(data, test = True): # if test take the first element, otherwise the second
|
|
540
|
+
return data[0] if test else data[1]
|
|
541
|
+
|
|
542
|
+
def apply_scale(data, test = False): # apply log scale if test
|
|
543
|
+
return log(data) if test else data
|
|
544
|
+
|
|
545
|
+
def reverse_scale(data, test = False): # apply log scale if test
|
|
546
|
+
return power10(data) if test else data
|
|
547
|
+
|
|
548
|
+
def replace_none(data, num_data): # replace None elements in data with correspondent in num_data
|
|
549
|
+
return [data[i] if data[i] is not None else num_data[i] for i in range(len(data))]
|
|
550
|
+
|
|
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
|
+
|
|
554
|
+
def get_lim(data): # it returns the data minimum and maximum limits
|
|
555
|
+
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]
|
|
560
|
+
|
|
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])
|
|
563
|
+
# round is so that for example 9.9999 = 10, otherwise the floor function will give different results
|
|
564
|
+
return [math.floor(round(change(el), 8)) if numerical(el) else el for el in data]
|
|
565
|
+
|
|
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
|
|
567
|
+
# if len(x) * len(y) == 0:
|
|
568
|
+
# return [], [], *[[]] * len(other)
|
|
569
|
+
o = transpose(other, len(other))
|
|
570
|
+
xl, yl, ol = [[] for i in range(3)]
|
|
571
|
+
for n in range(len(x) - 1):
|
|
572
|
+
xn, yn = x[n : n + 2], y[n : n + 2]
|
|
573
|
+
xn, yn = get_line(xn, yn)
|
|
574
|
+
xl += xn[:-1]
|
|
575
|
+
yl += yn[:-1]
|
|
576
|
+
ol += [o[n]] * len(xn[:-1])
|
|
577
|
+
xl = xl + [x[-1]] if x != [] else xl
|
|
578
|
+
yl = yl + [y[-1]] if x != [] else yl
|
|
579
|
+
ol = ol + [o[-1]] if x != [] else ol
|
|
580
|
+
return [xl, yl] + transpose(ol, len(other))
|
|
581
|
+
|
|
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.
|
|
583
|
+
if not all_numerical(join([x, y])):
|
|
584
|
+
return x, y
|
|
585
|
+
x0, x1 = x
|
|
586
|
+
y0, y1 = y
|
|
587
|
+
dx, dy = int(x1) - int(x0), int(y1) - int(y0)
|
|
588
|
+
ax, ay = abs(dx), abs(dy)
|
|
589
|
+
a = int(max(ax, ay) + 1)
|
|
590
|
+
x = [int(el) for el in linspace(x0, x1, a)]
|
|
591
|
+
y = [int(el) for el in linspace(y0, y1, a)]
|
|
592
|
+
return [x, y]
|
|
593
|
+
|
|
594
|
+
def get_fill_level(fill, lim, bins):
|
|
595
|
+
if fill is False:
|
|
596
|
+
return False
|
|
597
|
+
elif isinstance(fill, str):
|
|
598
|
+
return fill
|
|
599
|
+
else:
|
|
600
|
+
fill = min(max(fill, lim[0]), lim[1])
|
|
601
|
+
fill = get_matrix_data([fill], lim, bins)[0]
|
|
602
|
+
return fill
|
|
603
|
+
|
|
604
|
+
def find_filling_values(x, y, y0):
|
|
605
|
+
xn, yn, yf = [[]] * 3
|
|
606
|
+
l = len(x);
|
|
607
|
+
while len(x) > 0:
|
|
608
|
+
i = len(xn)
|
|
609
|
+
xn.append(x[i])
|
|
610
|
+
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]
|
|
618
|
+
yf.append(y[j])
|
|
619
|
+
return xn, yn, yf
|
|
620
|
+
|
|
621
|
+
def get_fill_boundaries(x, y):
|
|
622
|
+
xm = []
|
|
623
|
+
l = len(x)
|
|
624
|
+
for i in range(l):
|
|
625
|
+
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)
|
|
629
|
+
xm.append([x[i], m])
|
|
630
|
+
x, m = transpose(xm)
|
|
631
|
+
return m
|
|
632
|
+
|
|
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)
|
|
635
|
+
y0 = get_fill_boundaries(x, y) if isinstance(y0, str) else [y0] * len(x)
|
|
636
|
+
o = transpose(other, len(other))
|
|
637
|
+
xf, yf, of = [[] for i in range(3)]
|
|
638
|
+
xy = []
|
|
639
|
+
for i in range(len(x)):
|
|
640
|
+
xi, yi, y0i = x[i], y[i], y0[i]
|
|
641
|
+
if [xi, yi] not in xy:
|
|
642
|
+
xy.append([xi, yi])
|
|
643
|
+
yn = range(y0i, yi + 1) if y0i < yi else range(yi, y0i) if y0i > yi else [y0i]
|
|
644
|
+
yn = list(yn)
|
|
645
|
+
xn = [xi] * len(yn)
|
|
646
|
+
xf += xn
|
|
647
|
+
yf += yn
|
|
648
|
+
of += [o[i]] * len(xn)
|
|
649
|
+
return [xf, yf] + transpose(of, len(other))
|
|
650
|
+
|
|
651
|
+
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)]
|
|
653
|
+
o = transpose(other, len(other))
|
|
654
|
+
return transpose([(x[i], y[i], *o[i]) for i in I], 2 + len(other))
|
|
655
|
+
|
|
656
|
+
def get_labels(ticks): # it returns the approximated string version of the data ticks
|
|
657
|
+
d = distinguishing_digit(ticks)
|
|
658
|
+
formatting_string = "{:." + str(d + 1) + "f}"
|
|
659
|
+
labels = [formatting_string.format(el) for el in ticks]
|
|
660
|
+
pos = [el.index('.') + d + 2 for el in labels]
|
|
661
|
+
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))]
|
|
666
|
+
return labels
|
|
667
|
+
|
|
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)]
|
|
670
|
+
d = [_distinguishing_digit(data[i], data[i + 1]) for i in range(len(data) - 1)]
|
|
671
|
+
return max(d, default = 1)
|
|
672
|
+
|
|
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).
|
|
674
|
+
d = abs(a - b)
|
|
675
|
+
d = 0 if d == 0 else - math.log10(2 * d)
|
|
676
|
+
#d = round(d, 10)
|
|
677
|
+
d = 0 if d < 0 else math.ceil(d)
|
|
678
|
+
d = d + 1 if round(a, d) == round(b, d) else d
|
|
679
|
+
return d
|
|
680
|
+
|
|
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')
|
|
683
|
+
if zeros < d:
|
|
684
|
+
label += '0' * (d - zeros)
|
|
685
|
+
return label
|
|
686
|
+
|
|
687
|
+
def add_extra_spaces(labels, side): # it adds empty spaces before or after the labels if necessary
|
|
688
|
+
length = 0 if labels == [] else max_length(labels)
|
|
689
|
+
if side == "left":
|
|
690
|
+
labels = [space * (length - len(el)) + el for el in labels]
|
|
691
|
+
if side == "right":
|
|
692
|
+
labels = [el + space * (length - len(el)) for el in labels]
|
|
693
|
+
return labels
|
|
694
|
+
|
|
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)
|
|
697
|
+
xm = [el // xfm if numerical(el) else el for el in x]
|
|
698
|
+
ym = [el // yfm if numerical(el) else el for el in y]
|
|
699
|
+
m = {}
|
|
700
|
+
for i in range(l):
|
|
701
|
+
xyi = xm[i], ym[i]
|
|
702
|
+
xfi, yfi = xf[i], yf[i]
|
|
703
|
+
mi = [[0 for x in range(xfi)] for y in range(yfi)]
|
|
704
|
+
m[xyi] = mi
|
|
705
|
+
for i in range(l):
|
|
706
|
+
xyi = xm[i], ym[i]
|
|
707
|
+
if all_numerical(xyi):
|
|
708
|
+
xk, yk = x[i] % xfi, y[i] % yfi
|
|
709
|
+
xk, yk = math.floor(xk), math.floor(yk)
|
|
710
|
+
m[xyi][yk][xk] = 1
|
|
711
|
+
x, y = transpose(m.keys(), 2)
|
|
712
|
+
m = [tuple(join(el[::-1])) for el in m.values()]
|
|
713
|
+
return x, y, m
|
|
714
|
+
|
|
715
|
+
###############################################
|
|
716
|
+
############# Bar Functions ##############
|
|
717
|
+
###############################################
|
|
718
|
+
|
|
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
|
|
733
|
+
|
|
734
|
+
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]
|
|
744
|
+
# if data == []:
|
|
745
|
+
# return [], []
|
|
746
|
+
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]
|
|
749
|
+
data = [int(el) for el in data]
|
|
750
|
+
histx = linspace(m, M, bins)
|
|
751
|
+
histy = [0] * bins
|
|
752
|
+
for el in data:
|
|
753
|
+
histy[el] += 1
|
|
754
|
+
if norm:
|
|
755
|
+
histy = [el / len(data) for el in histy]
|
|
756
|
+
return histx, histy
|
|
757
|
+
|
|
758
|
+
def single_bar(x, y, ylabel, marker, colors):
|
|
759
|
+
l = len(y)
|
|
760
|
+
lc = len(colors)
|
|
761
|
+
xs = colorize(str(x), 'gray+', 'bold')
|
|
762
|
+
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
|
|
766
|
+
return bar
|
|
767
|
+
|
|
768
|
+
def bar_data(*args, width = None, mode = 'stacked'):
|
|
769
|
+
x, Y = set_multiple_bar_data(*args)
|
|
770
|
+
x = list(map(str, x))
|
|
771
|
+
x = add_extra_spaces(x, 'right')
|
|
772
|
+
lx = len(x[0])
|
|
773
|
+
y = [sum(el) for el in transpose(Y)] if mode == 'stacked' else Y
|
|
774
|
+
ly = max_length([round(el, 2) for el in join(y)])
|
|
775
|
+
|
|
776
|
+
width_term = terminal_width()
|
|
777
|
+
width = width_term if width is None else min(width, width_term)
|
|
778
|
+
width = max(width, lx + ly + 2 + 1)
|
|
779
|
+
|
|
780
|
+
my = max(join(y))
|
|
781
|
+
my = 1 if my == 0 else my
|
|
782
|
+
dx = my / (width - lx - ly - 2)
|
|
783
|
+
Yi = [[round(el / dx, 0) for el in y] for y in Y]
|
|
784
|
+
Yi = transpose(Yi)
|
|
785
|
+
|
|
786
|
+
return x, y, Yi, width
|
|
787
|
+
|
|
788
|
+
def correct_marker(marker = None):
|
|
789
|
+
return simple_bar_marker if marker is None else marker[0]
|
|
790
|
+
|
|
791
|
+
def get_title(title, width):
|
|
792
|
+
out = ''
|
|
793
|
+
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'
|
|
799
|
+
return out
|
|
800
|
+
|
|
801
|
+
def get_simple_labels(marker, labels, colors, width):
|
|
802
|
+
out = '\n'
|
|
803
|
+
if labels != None:
|
|
804
|
+
l = len(labels)
|
|
805
|
+
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)
|
|
808
|
+
return out
|
|
809
|
+
|
|
810
|
+
###############################################
|
|
811
|
+
############# Box Functions ##############
|
|
812
|
+
###############################################
|
|
813
|
+
|
|
814
|
+
def box(x, y, width, minimum): # given the bars center coordinates and height, it returns the full bar coordinates
|
|
815
|
+
# if x == []:
|
|
816
|
+
# return [], []
|
|
817
|
+
bins = len(x)
|
|
818
|
+
#bin_size_half = (max(x) - min(x)) / (bins - 1) * width / 2
|
|
819
|
+
bin_size_half = width / 2
|
|
820
|
+
# adjust the bar width according to the number of bins
|
|
821
|
+
if bins > 1:
|
|
822
|
+
bin_size_half *= (max(x) - min(x)) / (bins - 1)
|
|
823
|
+
c, q1, q2, q3, h, l = [], [], [], [], [], []
|
|
824
|
+
xbar, ybar, mybar = [], [], []
|
|
825
|
+
|
|
826
|
+
for i in range(bins):
|
|
827
|
+
c.append(x[i])
|
|
828
|
+
xbar.append([x[i] - bin_size_half, x[i] + bin_size_half])
|
|
829
|
+
q1.append(quantile(y[i], 0.25))
|
|
830
|
+
q2.append(quantile(y[i], 0.50))
|
|
831
|
+
q3.append(quantile(y[i], 0.75))
|
|
832
|
+
h.append(max(y[i]))
|
|
833
|
+
l.append(min(y[i]))
|
|
834
|
+
|
|
835
|
+
return q1, q2, q3, h, l, c, xbar
|
|
836
|
+
|
|
837
|
+
##############################################
|
|
838
|
+
########## Image Utilities #############
|
|
839
|
+
##############################################
|
|
840
|
+
|
|
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
|
|
842
|
+
size_old = [size_old[0], size_old[1] / 2]
|
|
843
|
+
ratio_old = size_old[1] / size_old[0]
|
|
844
|
+
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]
|
|
847
|
+
size_new = [1 if el == 0 else el for el in size_new]
|
|
848
|
+
return [int(size_new[0]), int(size_new[1])]
|
|
849
|
+
|
|
850
|
+
def image_to_matrix(image): # from image to a matrix of pixels
|
|
851
|
+
pixels = list(image.getdata())
|
|
852
|
+
width, height = image.size
|
|
853
|
+
return [pixels[i * width:(i + 1) * width] for i in range(height)]
|