unicode_plot 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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