Commit | Line | Data |
---|---|---|
846cf979 JD |
1 | #!/usr/bin/env python |
2 | # -*- coding: utf-8 -*- | |
3 | ||
4 | from __future__ import unicode_literals | |
5 | import sys | |
6 | ||
846cf979 | 7 | |
8054fc4c SG |
8 | class Pyasciigraph: |
9 | def __init__(self, line_length=79, min_graph_length=50, | |
10 | separator_length=2): | |
846cf979 | 11 | """Constructor of Pyasciigraph |
8054fc4c | 12 | |
846cf979 | 13 | :param int line_length: the max number of char on a line |
8054fc4c | 14 | if any line cannot be shorter, |
846cf979 JD |
15 | it will go over this limit |
16 | :param int min_graph_length: the min number of char used by the graph | |
17 | :param int separator_length: the length of field separator | |
18 | """ | |
19 | self.line_length = line_length | |
20 | self.separator_length = separator_length | |
21 | self.min_graph_length = min_graph_length | |
22 | ||
23 | def _u(self, x): | |
24 | if sys.version < '3': | |
25 | import codecs | |
26 | return codecs.unicode_escape_decode(x)[0] | |
27 | else: | |
28 | return x | |
29 | ||
30 | def _get_maximum(self, data): | |
31 | all_max = {} | |
32 | all_max['value_max_length'] = 0 | |
33 | all_max['info_max_length'] = 0 | |
34 | all_max['max_value'] = 0 | |
35 | ||
36 | for (info, value) in data: | |
37 | if value > all_max['max_value']: | |
38 | all_max['max_value'] = value | |
39 | ||
40 | if len(info) > all_max['info_max_length']: | |
41 | all_max['info_max_length'] = len(info) | |
8054fc4c | 42 | |
846cf979 JD |
43 | if len(str(value)) > all_max['value_max_length']: |
44 | all_max['value_max_length'] = len(str(value)) | |
45 | return all_max | |
46 | ||
47 | def _gen_graph_string(self, value, max_value, graph_length, start_value): | |
40fbd9cc JD |
48 | if max_value == 0: |
49 | number_of_square = int(value * graph_length) | |
50 | else: | |
51 | number_of_square = int(value * graph_length / max_value) | |
846cf979 JD |
52 | number_of_space = int(start_value - number_of_square) |
53 | return '█' * number_of_square + self._u(' ') * number_of_space | |
54 | ||
55 | def _gen_info_string(self, info, start_info, line_length): | |
56 | number_of_space = (line_length - start_info - len(info)) | |
57 | return info + self._u(' ') * number_of_space | |
58 | ||
bc9bd1f5 | 59 | def _gen_value_string(self, value, start_value, start_info, unit): |
846cf979 | 60 | number_space = start_info -\ |
8054fc4c SG |
61 | start_value -\ |
62 | len(str(value)) -\ | |
63 | self.separator_length | |
846cf979 | 64 | |
8054fc4c | 65 | return ' ' * number_space +\ |
bc9bd1f5 | 66 | str(value) + str(unit) +\ |
8054fc4c | 67 | ' ' * self.separator_length |
846cf979 JD |
68 | |
69 | def _sanitize_string(self, string): | |
8054fc4c | 70 | # get the type of a unicode string |
846cf979 JD |
71 | unicode_type = type(self._u('t')) |
72 | input_type = type(string) | |
73 | if input_type is str: | |
ddefe9a6 | 74 | if sys.version_info.major < 3: # pragma: no cover |
846cf979 | 75 | info = unicode(string) |
8054fc4c | 76 | else: |
846cf979 JD |
77 | info = string |
78 | elif input_type is unicode_type: | |
79 | info = string | |
80 | elif input_type is int or input_type is float: | |
ddefe9a6 | 81 | if sys.version_info.major < 3: # pragma: no cover |
846cf979 JD |
82 | info = unicode(string) |
83 | else: | |
84 | info = str(string) | |
85 | return info | |
86 | ||
87 | def _sanitize_data(self, data): | |
88 | ret = [] | |
89 | for item in data: | |
90 | ret.append((self._sanitize_string(item[0]), item[1])) | |
91 | return ret | |
92 | ||
bc9bd1f5 | 93 | def graph(self, label, data, sort=0, with_value=True, unit=""): |
846cf979 | 94 | """function generating the graph |
8054fc4c | 95 | |
846cf979 JD |
96 | :param string label: the label of the graph |
97 | :param iterable data: the data (list of tuple (info, value)) | |
98 | info must be "castable" to a unicode string | |
99 | value must be an int or a float | |
100 | :param int sort: flag sorted | |
101 | 0: not sorted (same order as given) (default) | |
102 | 1: increasing order | |
103 | 2: decreasing order | |
104 | :param boolean with_value: flag printing value | |
105 | True: print the numeric value (default) | |
106 | False: don't print the numeric value | |
107 | :rtype: a list of strings (each lines) | |
108 | ||
109 | """ | |
110 | result = [] | |
111 | san_data = self._sanitize_data(data) | |
112 | san_label = self._sanitize_string(label) | |
113 | ||
114 | if sort == 1: | |
8054fc4c SG |
115 | san_data = sorted(san_data, key=lambda value: value[1], |
116 | reverse=False) | |
846cf979 | 117 | elif sort == 2: |
8054fc4c SG |
118 | san_data = sorted(san_data, key=lambda value: value[1], |
119 | reverse=True) | |
846cf979 JD |
120 | |
121 | all_max = self._get_maximum(san_data) | |
8054fc4c | 122 | |
846cf979 | 123 | real_line_length = max(self.line_length, len(label)) |
8054fc4c | 124 | |
846cf979 | 125 | min_line_length = self.min_graph_length +\ |
8054fc4c SG |
126 | 2 * self.separator_length +\ |
127 | all_max['value_max_length'] +\ | |
128 | all_max['info_max_length'] | |
846cf979 JD |
129 | |
130 | if min_line_length < real_line_length: | |
8054fc4c | 131 | # calcul of where to start info |
846cf979 | 132 | start_info = self.line_length -\ |
8054fc4c SG |
133 | all_max['info_max_length'] |
134 | # calcul of where to start value | |
846cf979 | 135 | start_value = start_info -\ |
8054fc4c SG |
136 | self.separator_length -\ |
137 | all_max['value_max_length'] | |
138 | # calcul of where to end graph | |
846cf979 | 139 | graph_length = start_value -\ |
8054fc4c | 140 | self.separator_length |
846cf979 | 141 | else: |
8054fc4c | 142 | # calcul of where to start value |
846cf979 | 143 | start_value = self.min_graph_length +\ |
8054fc4c SG |
144 | self.separator_length |
145 | # calcul of where to start info | |
846cf979 | 146 | start_info = start_value +\ |
8054fc4c SG |
147 | all_max['value_max_length'] +\ |
148 | self.separator_length | |
149 | # calcul of where to end graph | |
846cf979 | 150 | graph_length = self.min_graph_length |
8054fc4c | 151 | # calcul of the real line length |
846cf979 JD |
152 | real_line_length = min_line_length |
153 | ||
154 | result.append(san_label) | |
8054fc4c | 155 | result.append(self._u('#') * real_line_length) |
846cf979 JD |
156 | |
157 | for item in san_data: | |
158 | info = item[0] | |
159 | value = item[1] | |
160 | ||
161 | graph_string = self._gen_graph_string( | |
8054fc4c SG |
162 | value, |
163 | all_max['max_value'], | |
164 | graph_length, | |
165 | start_value) | |
846cf979 | 166 | |
452b4312 JD |
167 | if with_value: |
168 | value_string = self._gen_value_string( | |
169 | value, | |
170 | start_value, | |
171 | start_info, unit) | |
172 | else: | |
173 | value_string = "" | |
846cf979 JD |
174 | |
175 | info_string = self._gen_info_string( | |
8054fc4c SG |
176 | info, |
177 | start_info, | |
178 | real_line_length) | |
846cf979 JD |
179 | new_line = graph_string + value_string + info_string |
180 | result.append(new_line) | |
181 | ||
182 | return result | |
183 | ||
184 | if __name__ == '__main__': | |
8054fc4c SG |
185 | test = [('long_label', 423), ('sl', 1234), ('line3', 531), |
186 | ('line4', 200), ('line5', 834)] | |
846cf979 | 187 | graph = Pyasciigraph() |
8054fc4c | 188 | for line in graph.graph('test print', test): |
846cf979 | 189 | print(line) |