1 /*
2   Purpose : Common function to deal with bar chats
3   Author  : Ky-Anh Huynh
4   License : MIT
5   Date    : 2017-Sep-09th
6 */
7 
8 module dusybox.plot.tobars;
9 
10 import std.algorithm;
11 import std.array: array;
12 import std.conv;
13 import std.math;
14 import std.stdio;
15 import std.format;
16 import std.string;
17 
18 struct Bar {
19   string  key;
20   uint    len;
21   double  value;
22 
23   // FIXME: This should be a private value...
24   // FIXME: Has ability to reset max_key_width for next iteraction
25   static
26   ulong   max_key_width = 0;
27 
28   this(in string key, in double len, in double value) {
29     this.key = key;
30     this.len = roundTo!uint(len);
31     this.value = value;
32     if (key.length > this.max_key_width) {
33       this.max_key_width = key.length;
34     }
35     debug(2) stderr.writefln(format!"(debug) New Bar: key = %s (w = %d), len = %s, value = %s"(this.key, this.max_key_width, this.len, this.value));
36   }
37 
38   string toString() const {
39     auto bar_st = leftJustify("", len, '=');
40     auto key_st = rightJustify(key, max_key_width, ' ');
41     return format!"%s : %3d %% %s (%s)"(key_st, len, bar_st, roundTo!size_t(value));
42   }
43 
44   static
45   void reset() {
46     max_key_width = 0;
47   }
48 
49   bool opEquals(in Bar rhs) const {
50     return (key == rhs.key) && (len == rhs.len);
51   }
52 }
53 
54 Bar[] tobars(in double[string] plotdata, in uint min_percent = 0) {
55   Bar[] results;
56 
57   auto sum_input = plotdata.byValue.sum();
58 
59   if (plotdata.length < 1 || sum_input == 0) {
60     debug stderr.writefln(":: Plot data (size %d) is empty or all entries are equal to 0.", plotdata.length);
61     return results;
62   }
63 
64   foreach (key, value; plotdata) {
65     auto len = value / sum_input * 100;
66     // FIXME: How to avoid to create new object Bar if len < min_percent?
67     if (len < min_percent) {
68       continue;
69     }
70     results ~= Bar(key, len, value);
71   }
72 
73   return results;
74 }
75 
76 unittest {
77   import std.array: array;
78 
79   double[string] empty_arr;
80   Bar[] empty_result;
81 
82   assert(empty_arr.tobars               == empty_result, "Empty input should return empty array of Bar.");
83   assert(["foo1": 0, "bar1": 0].tobars  == empty_result, "Zero sum should return empty array of Bar.");
84 
85   assert(["foo": 1].tobars    == [Bar("foo", 100, 1)], "Single bar entry with integer value.");
86   assert(["foo": 1.1].tobars  == [Bar("foo", 100, 1)], "Single bar entry with floating input value.");
87 
88   auto results = ["foo1": 1, "bar1": 1].tobars;
89   results.format!"%-(%s\n%)".writeln;
90   assert(results[0].len == results[1].len);
91 
92   results = ["foo2": 1, "bar2": 1.2].tobars;
93   results.format!"%-(%s\n%)".writeln;
94   assert(results[0].len == 45 || results[0].len == 55);
95   assert(results[1].len == 45 || results[1].len == 55);
96 
97   assert(Bar.max_key_width > 0);
98   Bar.reset();
99   assert(Bar.max_key_width == 0);
100 }