Refactor in a single package with subpackages
[deliverable/lttng-analyses.git] / lttnganalyses / ascii_graph / __init__.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2012 Pierre-Francois Carpentier <carpentier.pf@gmail.com>
4 #
5 # https://github.com/kakwa/py-ascii-graph/
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be
16 # included in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26 from __future__ import unicode_literals
27 import sys
28 import os
29
30
31 class Pyasciigraph:
32 def __init__(self, line_length=79, min_graph_length=50,
33 separator_length=2):
34 """Constructor of Pyasciigraph
35
36 :param int line_length: the max number of char on a line
37 if any line cannot be shorter,
38 it will go over this limit
39 :param int min_graph_length: the min number of char used by the graph
40 :param int separator_length: the length of field separator
41 """
42 self.line_length = line_length
43 self.separator_length = separator_length
44 self.min_graph_length = min_graph_length
45
46 def _u(self, x):
47 if sys.version < '3':
48 import codecs
49 return codecs.unicode_escape_decode(x)[0]
50 else:
51 return x
52
53 def _get_maximum(self, data):
54 all_max = {}
55 all_max['value_max_length'] = 0
56 all_max['info_max_length'] = 0
57 all_max['max_value'] = 0
58
59 for (info, value) in data:
60 if value > all_max['max_value']:
61 all_max['max_value'] = value
62
63 if len(info) > all_max['info_max_length']:
64 all_max['info_max_length'] = len(info)
65
66 if len(str(value)) > all_max['value_max_length']:
67 all_max['value_max_length'] = len(str(value))
68 return all_max
69
70 def _gen_graph_string(self, value, max_value, graph_length, start_value):
71 if max_value == 0:
72 number_of_square = int(value * graph_length)
73 else:
74 number_of_square = int(value * graph_length / max_value)
75 number_of_space = int(start_value - number_of_square)
76 return '█' * number_of_square + self._u(' ') * number_of_space
77
78 def _console_size(self):
79 TERMSIZE = 80
80 return int(os.environ.get('COLUMNS', TERMSIZE)) - 1
81
82 def _gen_info_string(self, info, start_info, line_length, info_before):
83 number_of_space = (line_length - start_info - len(info))
84 if info_before:
85 return self._u(' ') * number_of_space + info
86 else:
87 return info + self._u(' ') * number_of_space
88
89 def _gen_value_string(self, value, start_value, start_info, unit, count):
90 if not count:
91 v = str("%0.02f" % value)
92 else:
93 # we don't want to add .00 to count values (only integers)
94 v = str(value)
95 number_space = start_info -\
96 start_value -\
97 len(v) -\
98 self.separator_length
99
100 return ' ' * number_space +\
101 v + str(unit) +\
102 ' ' * self.separator_length
103
104 def _sanitize_string(self, string):
105 # get the type of a unicode string
106 unicode_type = type(self._u('t'))
107 input_type = type(string)
108 if input_type is str:
109 if sys.version_info.major < 3: # pragma: no cover
110 info = string
111 else:
112 info = string
113 elif input_type is unicode_type:
114 info = string
115 elif input_type is int or input_type is float:
116 if sys.version_info.major < 3: # pragma: no cover
117 info = string
118 else:
119 info = str(string)
120 return info
121
122 def _sanitize_data(self, data):
123 ret = []
124 for item in data:
125 ret.append((self._sanitize_string(item[0]), item[1]))
126 return ret
127
128 def graph(self, label, data, sort=0, with_value=True, unit="",
129 info_before=False, count=False):
130 """function generating the graph
131
132 :param string label: the label of the graph
133 :param iterable data: the data (list of tuple (info, value))
134 info must be "castable" to a unicode string
135 value must be an int or a float
136 :param int sort: flag sorted
137 0: not sorted (same order as given) (default)
138 1: increasing order
139 2: decreasing order
140 :param boolean with_value: flag printing value
141 True: print the numeric value (default)
142 False: don't print the numeric value
143 :rtype: a list of strings (each lines)
144
145 """
146 result = []
147 san_data = self._sanitize_data(data)
148 san_label = self._sanitize_string(label)
149
150 if sort == 1:
151 san_data = sorted(san_data, key=lambda value: value[1],
152 reverse=False)
153 elif sort == 2:
154 san_data = sorted(san_data, key=lambda value: value[1],
155 reverse=True)
156
157 all_max = self._get_maximum(san_data)
158
159 real_line_length = max(self.line_length, len(label))
160
161 min_line_length = self.min_graph_length +\
162 2 * self.separator_length +\
163 all_max['value_max_length'] +\
164 all_max['info_max_length']
165
166 if min_line_length < real_line_length:
167 # calcul of where to start info
168 start_info = self.line_length -\
169 all_max['info_max_length']
170 # calcul of where to start value
171 start_value = start_info -\
172 self.separator_length -\
173 all_max['value_max_length']
174 # calcul of where to end graph
175 graph_length = start_value -\
176 self.separator_length
177 else:
178 # calcul of where to start value
179 start_value = self.min_graph_length +\
180 self.separator_length
181 # calcul of where to start info
182 start_info = start_value +\
183 all_max['value_max_length'] +\
184 self.separator_length
185 # calcul of where to end graph
186 graph_length = self.min_graph_length
187 # calcul of the real line length
188 real_line_length = min_line_length
189
190 real_line_length = min(real_line_length, self._console_size())
191 result.append(san_label)
192 result.append(self._u('#') * real_line_length)
193
194 for item in san_data:
195 info = item[0]
196 value = item[1]
197
198 graph_string = self._gen_graph_string(
199 value,
200 all_max['max_value'],
201 graph_length,
202 start_value)
203
204 if with_value:
205 value_string = self._gen_value_string(
206 value,
207 start_value,
208 start_info, unit, count)
209 else:
210 value_string = ""
211
212 info_string = self._gen_info_string(
213 info,
214 start_info,
215 real_line_length, info_before)
216 if info_before:
217 new_line = info_string + " " + graph_string + value_string
218 else:
219 new_line = graph_string + value_string + info_string
220 result.append(new_line)
221
222 return result
223
224 if __name__ == '__main__':
225 test = [('long_label', 423), ('sl', 1234), ('line3', 531),
226 ('line4', 200), ('line5', 834)]
227 graph = Pyasciigraph()
228 for line in graph.graph('test print', test):
229 print(line)
This page took 0.04071 seconds and 5 git commands to generate.