unicode_plot 0.0.2 → 0.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30cc04427f90664c0157ae18002b8e241cb54d232b33f66edc72b0e6b2dac3d7
4
- data.tar.gz: 05c0bfc5b3d2c910bb39f655fb1b10f44c76a552b6e87ede46b4b3f3d04055ca
3
+ metadata.gz: b33753adec2204f206dff50057dbf735be77c5d1596d8386b103058e76355ba2
4
+ data.tar.gz: 3209b35c716a2e8293735a6f774185f12c6627996707fb28c59fec3ac02debd7
5
5
  SHA512:
6
- metadata.gz: 6111ed2d7cc47821286ff5da5a33660c24298a04eaeb56377717aa548b9ba150a4c2b67e0ccf62b4aa36c577413babf0bf7fdc50fd37b6945142628ad0d9ed5a
7
- data.tar.gz: 0d11730310f093b679a71ca4bb00b760d10e127030cbe797cbf7a171094e192908a3bb3cf7d9de2ba83527ccd8b60ce8f5dbb1956870741aa4a4f55b5c45ac95
6
+ metadata.gz: dc5c517d8bdbce3d622767d03e4775365d51ac8a1e882e96d4aaba16c05345c66f0431be9f1eb79ad5cbcbfdfb92e395f06dba5f8f18c8a98b9f93491c25235d
7
+ data.tar.gz: b5aa69592cf09b140c1dff55edeafbc8204929fe99125dd67d31218286157f4beaa93051da151e5efddcefd24054ec08d458b1aeeee0ac0acabe26813b348b48
data/README.md CHANGED
@@ -30,7 +30,10 @@ You can get the results below by running the above script:
30
30
 
31
31
  - barplot
32
32
  - boxplot
33
+ - densityplot
34
+ - histogram
33
35
  - lineplot
36
+ - scatterplot
34
37
 
35
38
  ## Acknowledgement
36
39
 
@@ -1,5 +1,5 @@
1
1
  module UnicodePlot
2
- class AsciiCanvas < Canvas
2
+ class AsciiCanvas < LookupCanvas
3
3
  ASCII_SIGNS = [
4
4
  [ 0b100_000_000, 0b000_100_000, 0b000_000_100 ].freeze,
5
5
  [ 0b010_000_000, 0b000_010_000, 0b000_000_010 ].freeze,
@@ -105,47 +105,10 @@ module UnicodePlot
105
105
 
106
106
  def initialize(width, height, **kw)
107
107
  super(width, height,
108
- width * PIXEL_PER_CHAR,
109
- height * PIXEL_PER_CHAR,
110
- 0,
111
- x_pixel_per_char: PIXEL_PER_CHAR,
112
- y_pixel_per_char: PIXEL_PER_CHAR,
108
+ PIXEL_PER_CHAR, PIXEL_PER_CHAR,
113
109
  **kw)
114
110
  end
115
111
 
116
- def pixel!(pixel_x, pixel_y, color)
117
- unless 0 <= pixel_x && pixel_x <= pixel_width &&
118
- 0 <= pixel_y && pixel_y <= pixel_height
119
- return color
120
- end
121
- pixel_x -= 1 unless pixel_x < pixel_width
122
- pixel_y -= 1 unless pixel_y < pixel_height
123
-
124
- tx = pixel_x.fdiv(pixel_width) * width
125
- char_x = tx.floor + 1
126
- char_x_off = pixel_x % PIXEL_PER_CHAR + 1
127
- char_x += 1 if char_x < tx.round + 1 && char_x_off == 1
128
-
129
- char_y = (pixel_y.fdiv(pixel_height) * height).floor + 1
130
- char_y_off = pixel_y % PIXEL_PER_CHAR + 1
131
-
132
- index = index_at(char_x - 1, char_y - 1)
133
- if index
134
- @grid[index] |= lookup_encode(char_x_off - 1, char_y_off - 1)
135
- @colors[index] |= COLOR_ENCODE[color]
136
- end
137
- end
138
-
139
- def print_row(out, row_index)
140
- unless 0 <= row_index && row_index < height
141
- raise ArgumentError, "row_index out of bounds"
142
- end
143
- y = row_index
144
- (0 ... width).each do |x|
145
- print_color(out, color_at(x, y), lookup_decode(char_at(x, y)))
146
- end
147
- end
148
-
149
112
  def lookup_encode(x, y)
150
113
  ASCII_SIGNS[x][y]
151
114
  end
@@ -34,13 +34,19 @@ module UnicodePlot
34
34
  @width
35
35
  end
36
36
 
37
+ def add_row!(bars)
38
+ @bars.concat(bars)
39
+ @max_freq, i = find_max(transform_values(@transform, bars))
40
+ @max_len = @bars[i].to_s.length
41
+ end
42
+
37
43
  def print_row(out, row_index)
38
44
  check_row_index(row_index)
39
45
  bar = @bars[row_index]
40
46
  max_bar_width = [width - 2 - max_len, 1].max
41
47
  val = transform_values(@transform, bar)
42
48
  bar_len = max_freq > 0 ?
43
- ([val, 0.0].max.fdiv(max_freq) * max_bar_width).round :
49
+ ([val, 0].max.fdiv(max_freq) * max_bar_width).round :
44
50
  0
45
51
  bar_str = max_freq > 0 ? @symbol * bar_len : ""
46
52
  bar_lbl = bar.to_s
@@ -81,7 +87,7 @@ module UnicodePlot
81
87
  **kw)
82
88
  case args.length
83
89
  when 0
84
- data = Hash(args[0] || data)
90
+ data = Hash(data)
85
91
  keys = data.keys.map(&:to_s)
86
92
  heights = data.values
87
93
  when 2
@@ -91,6 +97,13 @@ module UnicodePlot
91
97
  raise ArgumentError, "invalid arguments"
92
98
  end
93
99
 
100
+ unless keys.length == heights.length
101
+ raise ArgumentError, "The given vectors must be of the same length"
102
+ end
103
+ unless heights.min >= 0
104
+ raise ArgumentError, "All values have to be positive. Negative bars are not supported."
105
+ end
106
+
94
107
  xlabel ||= ValueTransformer.transform_name(xscale)
95
108
  plot = Barplot.new(heights, width, color, symbol, xscale,
96
109
  border: border, xlabel: xlabel,
@@ -101,4 +114,35 @@ module UnicodePlot
101
114
 
102
115
  plot
103
116
  end
117
+
118
+ module_function def barplot!(plot,
119
+ *args,
120
+ data: nil,
121
+ **kw)
122
+ case args.length
123
+ when 0
124
+ data = Hash(data)
125
+ keys = data.keys.map(&:to_s)
126
+ heights = data.values
127
+ when 2
128
+ keys = Array(args[0])
129
+ heights = Array(args[1])
130
+ else
131
+ raise ArgumentError, "invalid arguments"
132
+ end
133
+
134
+ unless keys.length == heights.length
135
+ raise ArgumentError, "The given vectors must be of the same length"
136
+ end
137
+ if keys.empty?
138
+ raise ArgumentError, "Can't append empty array to barplot"
139
+ end
140
+
141
+ cur_idx = plot.n_rows
142
+ plot.add_row!(heights)
143
+ keys.each_with_index do |key, i|
144
+ plot.annotate_row!(:l, cur_idx + i, key)
145
+ end
146
+ plot
147
+ end
104
148
  end
@@ -53,7 +53,6 @@ module UnicodePlot
53
53
 
54
54
  def print_row(out, row_index)
55
55
  unless 0 <= row_index && row_index < height
56
- $stderr.puts [row_index, height].inspect
57
56
  raise ArgumentError, "row_index out of bounds"
58
57
  end
59
58
  y = row_index
@@ -8,6 +8,10 @@ module UnicodePlot
8
8
  AsciiCanvas.new(width, height, **kw)
9
9
  when :braille
10
10
  BrailleCanvas.new(width, height, **kw)
11
+ when :density
12
+ DensityCanvas.new(width, height, **kw)
13
+ when :dot
14
+ DotCanvas.new(width, height, **kw)
11
15
  else
12
16
  raise ArgumentError, "unknown canvas type: #{canvas_type}"
13
17
  end
@@ -22,8 +26,8 @@ module UnicodePlot
22
26
  y_pixel_per_char: 1)
23
27
  @width = width
24
28
  @height = height
25
- @pixel_width = pixel_width
26
- @pixel_height = pixel_height
29
+ @pixel_width = check_positive(pixel_width, :pixel_width)
30
+ @pixel_height = check_positive(pixel_height, :pixel_height)
27
31
  @origin_x = origin_x
28
32
  @origin_y = origin_y
29
33
  @plot_width = plot_width
@@ -83,7 +87,7 @@ module UnicodePlot
83
87
  def point!(x, y, color)
84
88
  unless origin_x <= x && x <= origin_x + plot_width &&
85
89
  origin_y <= y && y <= origin_y + plot_height
86
- return c
90
+ return color
87
91
  end
88
92
 
89
93
  plot_offset_x = x - origin_x
@@ -156,5 +160,10 @@ module UnicodePlot
156
160
  line!(x[i], y[i], x[i+1], y[i+1], color)
157
161
  end
158
162
  end
163
+
164
+ private def check_positive(value, name)
165
+ return value if value > 0
166
+ raise ArgumentError, "#{name} has to be positive"
167
+ end
159
168
  end
160
169
  end
@@ -0,0 +1,56 @@
1
+ module UnicodePlot
2
+ class DensityCanvas < Canvas
3
+ DENSITY_SIGNS = [" ", "░", "▒", "▓", "█"].freeze
4
+
5
+ MIN_WIDTH = 5
6
+ MIN_HEIGHT = 5
7
+
8
+ X_PIXEL_PER_CHAR = 1
9
+ Y_PIXEL_PER_CHAR = 2
10
+
11
+ def initialize(width, height, **kw)
12
+ width = [width, MIN_WIDTH].max
13
+ height = [height, MIN_HEIGHT].max
14
+ @max_density = 1
15
+ super(width, height,
16
+ width * X_PIXEL_PER_CHAR,
17
+ height * Y_PIXEL_PER_CHAR,
18
+ 0,
19
+ x_pixel_per_char: X_PIXEL_PER_CHAR,
20
+ y_pixel_per_char: Y_PIXEL_PER_CHAR,
21
+ **kw)
22
+ end
23
+
24
+ def pixel!(pixel_x, pixel_y, color)
25
+ unless 0 <= pixel_x && pixel_x <= pixel_width &&
26
+ 0 <= pixel_y && pixel_y <= pixel_height
27
+ return color
28
+ end
29
+
30
+ pixel_x -= 1 unless pixel_x < pixel_width
31
+ pixel_y -= 1 unless pixel_y < pixel_height
32
+
33
+ char_x = (pixel_x.fdiv(pixel_width) * width).floor
34
+ char_y = (pixel_y.fdiv(pixel_height) * height).floor
35
+
36
+ index = index_at(char_x, char_y)
37
+ @grid[index] += 1
38
+ @max_density = [@max_density, @grid[index]].max
39
+ @colors[index] |= COLOR_ENCODE[color]
40
+ color
41
+ end
42
+
43
+ def print_row(out, row_index)
44
+ unless 0 <= row_index && row_index < height
45
+ raise ArgumentError, "row_index out of bounds"
46
+ end
47
+ y = row_index
48
+ den_sign_count = DENSITY_SIGNS.length
49
+ val_scale = (den_sign_count - 1).fdiv(@max_density)
50
+ (0 ... width).each do |x|
51
+ den_index = (char_at(x, y) * val_scale).round
52
+ print_color(out, color_at(x, y), DENSITY_SIGNS[den_index])
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,10 @@
1
+ module UnicodePlot
2
+ module_function def densityplot(x, y, color: :auto, grid: false, name: "", **kw)
3
+ plot = GridPlot.new(x, y, :density, grid: grid, **kw)
4
+ scatterplot!(plot, x, y, color: color, name: name)
5
+ end
6
+
7
+ module_function def densityplot!(plot, x, y, **kw)
8
+ scatterplot!(plot, x, y, **kw)
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ module UnicodePlot
2
+ class DotCanvas < LookupCanvas
3
+ DOT_SIGNS = [
4
+ [
5
+ 0b10,
6
+ 0b01
7
+ ].freeze
8
+ ].freeze
9
+
10
+ DOT_DECODE = [
11
+ -' ', # 0b00
12
+ -'.', # 0b01
13
+ -"'", # 0b10
14
+ -':', # 0b11
15
+ ].freeze
16
+
17
+ X_PIXEL_PER_CHAR = 1
18
+ Y_PIXEL_PER_CHAR = 2
19
+
20
+ def initialize(width, height, **kw)
21
+ super(width, height,
22
+ X_PIXEL_PER_CHAR, Y_PIXEL_PER_CHAR,
23
+ **kw)
24
+ end
25
+
26
+ def lookup_encode(x, y)
27
+ DOT_SIGNS[x][y]
28
+ end
29
+
30
+ def lookup_decode(code)
31
+ DOT_DECODE[code]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,101 @@
1
+ module UnicodePlot
2
+ class GridPlot < Plot
3
+ MIN_WIDTH = 5
4
+ DEFAULT_WIDTH = 40
5
+ MIN_HEIGHT = 2
6
+ DEFAULT_HEIGHT = 15
7
+
8
+ def initialize(x, y, canvas,
9
+ width: DEFAULT_WIDTH,
10
+ height: DEFAULT_HEIGHT,
11
+ xlim: [0, 0],
12
+ ylim: [0, 0],
13
+ grid: true,
14
+ **kw)
15
+ if x.length != y.length
16
+ raise ArgumentError, "x and y must be the same length"
17
+ end
18
+ unless x.length > 0
19
+ raise ArgumentError, "x and y must not be empty"
20
+ end
21
+ unless xlim.length == 2 && ylim.length == 2
22
+ raise ArgumentError, "xlim and ylim must be 2-length arrays"
23
+ end
24
+ width = [width, MIN_WIDTH].max
25
+ height = [height, MIN_HEIGHT].max
26
+ min_x, max_x = Utils.extend_limits(x, xlim)
27
+ min_y, max_y = Utils.extend_limits(y, ylim)
28
+ origin_x = min_x
29
+ origin_y = min_y
30
+ plot_width = max_x - origin_x
31
+ plot_height = max_y - origin_y
32
+ @canvas = Canvas.create(canvas, width, height,
33
+ origin_x: origin_x,
34
+ origin_y: origin_y,
35
+ plot_width: plot_width,
36
+ plot_height: plot_height)
37
+ super(**kw)
38
+
39
+ min_x_str = (Utils.roundable?(min_x) ? min_x.round : min_x).to_s
40
+ max_x_str = (Utils.roundable?(max_x) ? max_x.round : max_x).to_s
41
+ min_y_str = (Utils.roundable?(min_y) ? min_y.round : min_y).to_s
42
+ max_y_str = (Utils.roundable?(max_y) ? max_y.round : max_y).to_s
43
+
44
+ annotate_row!(:l, 0, max_y_str, color: :light_black)
45
+ annotate_row!(:l, height-1, min_y_str, color: :light_black)
46
+ annotate!(:bl, min_x_str, color: :light_black)
47
+ annotate!(:br, max_x_str, color: :light_black)
48
+
49
+ if grid
50
+ if min_y < 0 && 0 < max_y
51
+ step = plot_width.fdiv(width * @canvas.x_pixel_per_char - 1)
52
+ min_x.step(max_x, by: step) do |i|
53
+ @canvas.point!(i, 0, :normal)
54
+ end
55
+ end
56
+ if min_x < 0 && 0 < max_x
57
+ step = plot_height.fdiv(height * @canvas.y_pixel_per_char - 1)
58
+ min_y.step(max_y, by: step) do |i|
59
+ @canvas.point!(0, i, :normal)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def origin_x
66
+ @canvas.origin_x
67
+ end
68
+
69
+ def origin_y
70
+ @canvas.origin_y
71
+ end
72
+
73
+ def plot_width
74
+ @canvas.plot_width
75
+ end
76
+
77
+ def plot_height
78
+ @canvas.plot_height
79
+ end
80
+
81
+ def n_rows
82
+ @canvas.height
83
+ end
84
+
85
+ def n_columns
86
+ @canvas.width
87
+ end
88
+
89
+ def points!(x, y, color)
90
+ @canvas.points!(x, y, color)
91
+ end
92
+
93
+ def lines!(x, y, color)
94
+ @canvas.lines!(x, y, color)
95
+ end
96
+
97
+ def print_row(out, row_index)
98
+ @canvas.print_row(out, row_index)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,48 @@
1
+ require 'enumerable/statistics'
2
+
3
+ module UnicodePlot
4
+ module_function def histogram(x,
5
+ nbins: nil,
6
+ closed: :left,
7
+ symbol: "▇",
8
+ **kw)
9
+ hist = x.histogram(*[nbins].compact, closed: closed)
10
+ edge, counts = hist.edge, hist.weights
11
+ labels = []
12
+ bin_width = edge[1] - edge[0]
13
+ pad_left, pad_right = 0, 0
14
+ (0 ... edge.length).each do |i|
15
+ val1 = Utils.float_round_log10(edge[i], bin_width)
16
+ val2 = Utils.float_round_log10(val1 + bin_width, bin_width)
17
+ a1 = val1.to_s.split('.', 2).map(&:length)
18
+ a2 = val2.to_s.split('.', 2).map(&:length)
19
+ pad_left = [pad_left, a1[0], a2[0]].max
20
+ pad_right = [pad_right, a1[1], a2[1]].max
21
+ end
22
+ l_str = hist.closed == :right ? "(" : "["
23
+ r_str = hist.closed == :right ? "]" : ")"
24
+ counts.each_with_index do |n, i|
25
+ val1 = Utils.float_round_log10(edge[i], bin_width)
26
+ val2 = Utils.float_round_log10(val1 + bin_width, bin_width)
27
+ a1 = val1.to_s.split('.', 2).map(&:length)
28
+ a2 = val2.to_s.split('.', 2).map(&:length)
29
+ labels[i] = "\e[90m#{l_str}\e[0m" +
30
+ (" " * (pad_left - a1[0])) +
31
+ val1.to_s +
32
+ (" " * (pad_right - a1[1])) +
33
+ "\e[90m, \e[0m" +
34
+ (" " * (pad_left - a2[0])) +
35
+ val2.to_s +
36
+ (" " * (pad_right - a2[1])) +
37
+ "\e[90m#{r_str}\e[0m"
38
+ end
39
+ xscale = kw.delete(:xscale)
40
+ xlabel = kw.delete(:xlabel) ||
41
+ ValueTransformer.transform_name(xscale, "Frequency")
42
+ barplot(labels, counts,
43
+ symbol: symbol,
44
+ xscale: xscale,
45
+ xlabel: xlabel,
46
+ **kw)
47
+ end
48
+ end