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.
@@ -1,111 +1,7 @@
1
1
  require 'date'
2
2
 
3
3
  module UnicodePlot
4
- class GridCanvas < Plot
5
- MIN_WIDTH = 5
6
- DEFAULT_WIDTH = 40
7
- MIN_HEIGHT = 2
8
- DEFAULT_HEIGHT = 15
9
-
10
- def initialize(x, y, canvas,
11
- width: DEFAULT_WIDTH,
12
- height: DEFAULT_HEIGHT,
13
- xlim: [0, 0],
14
- ylim: [0, 0],
15
- grid: true,
16
- **kw)
17
- unless xlim.length == 2 && ylim.length == 2
18
- raise ArgumentError, "xlim and ylim must be 2-length arrays"
19
- end
20
- width = [width, MIN_WIDTH].max
21
- height = [height, MIN_HEIGHT].max
22
- min_x, max_x = Utils.extend_limits(x, xlim)
23
- min_y, max_y = Utils.extend_limits(y, ylim)
24
- origin_x = min_x
25
- origin_y = min_y
26
- plot_width = max_x - origin_x
27
- plot_height = max_y - origin_y
28
- @canvas = Canvas.create(canvas, width, height,
29
- origin_x: origin_x,
30
- origin_y: origin_y,
31
- plot_width: plot_width,
32
- plot_height: plot_height)
33
- super(**kw)
34
-
35
- min_x_str = (Utils.roundable?(min_x) ? min_x.round : min_x).to_s
36
- max_x_str = (Utils.roundable?(max_x) ? max_x.round : max_x).to_s
37
- min_y_str = (Utils.roundable?(min_y) ? min_y.round : min_y).to_s
38
- max_y_str = (Utils.roundable?(max_y) ? max_y.round : max_y).to_s
39
-
40
- annotate_row!(:l, 0, max_y_str, color: :light_black)
41
- annotate_row!(:l, height-1, min_y_str, color: :light_black)
42
- annotate!(:bl, min_x_str, color: :light_black)
43
- annotate!(:br, max_x_str, color: :light_black)
44
-
45
- if grid
46
- if min_y < 0 && 0 < max_y
47
- step = plot_width.fdiv(width * @canvas.x_pixel_per_char - 1)
48
- min_x.step(max_x, by: step) do |i|
49
- @canvas.point!(i, 0, :normal)
50
- end
51
- end
52
- if min_x < 0 && 0 < max_x
53
- step = plot_height.fdiv(height * @canvas.y_pixel_per_char - 1)
54
- min_y.step(max_y, by: step) do |i|
55
- @canvas.point!(0, i, :normal)
56
- end
57
- end
58
- end
59
- end
60
-
61
- def origin_x
62
- @canvas.origin_x
63
- end
64
-
65
- def origin_y
66
- @canvas.origin_y
67
- end
68
-
69
- def plot_width
70
- @canvas.plot_width
71
- end
72
-
73
- def plot_height
74
- @canvas.plot_height
75
- end
76
-
77
- def n_rows
78
- @canvas.height
79
- end
80
-
81
- def n_columns
82
- @canvas.width
83
- end
84
-
85
- def points!(x, y, color)
86
- @canvas.points!(x, y, color)
87
- end
88
-
89
- def lines!(x, y, color)
90
- @canvas.lines!(x, y, color)
91
- end
92
-
93
- def print_row(out, row_index)
94
- @canvas.print_row(out, row_index)
95
- end
96
- end
97
-
98
- class Lineplot < GridCanvas
99
- def initialize(x, y, canvas,
100
- **kw)
101
- if x.length != y.length
102
- raise ArgumentError, "x and y must be the same length"
103
- end
104
- unless x.length > 0
105
- raise ArgumentError, "x and y must not be empty"
106
- end
107
- super(x, y, canvas, **kw)
108
- end
4
+ class Lineplot < GridPlot
109
5
  end
110
6
 
111
7
  module_function def lineplot(*args,
@@ -0,0 +1,46 @@
1
+ module UnicodePlot
2
+ class LookupCanvas < Canvas
3
+ def initialize(width, height, x_pixel_per_char, y_pixel_per_char, fill_char=0, **kw)
4
+ super(width, height,
5
+ width * x_pixel_per_char,
6
+ height * y_pixel_per_char,
7
+ fill_char,
8
+ x_pixel_per_char: x_pixel_per_char,
9
+ y_pixel_per_char: y_pixel_per_char,
10
+ **kw)
11
+ end
12
+
13
+ def pixel!(pixel_x, pixel_y, color)
14
+ unless 0 <= pixel_x && pixel_x <= pixel_width &&
15
+ 0 <= pixel_y && pixel_y <= pixel_height
16
+ return color
17
+ end
18
+ pixel_x -= 1 unless pixel_x < pixel_width
19
+ pixel_y -= 1 unless pixel_y < pixel_height
20
+
21
+ tx = pixel_x.fdiv(pixel_width) * width
22
+ char_x = tx.floor + 1
23
+ char_x_off = pixel_x % x_pixel_per_char + 1
24
+ char_x += 1 if char_x < tx.round + 1 && char_x_off == 1
25
+
26
+ char_y = (pixel_y.fdiv(pixel_height) * height).floor + 1
27
+ char_y_off = pixel_y % y_pixel_per_char + 1
28
+
29
+ index = index_at(char_x - 1, char_y - 1)
30
+ if index
31
+ @grid[index] |= lookup_encode(char_x_off - 1, char_y_off - 1)
32
+ @colors[index] |= COLOR_ENCODE[color]
33
+ end
34
+ end
35
+
36
+ def print_row(out, row_index)
37
+ unless 0 <= row_index && row_index < height
38
+ raise ArgumentError, "row_index out of bounds"
39
+ end
40
+ y = row_index
41
+ (0 ... width).each do |x|
42
+ print_color(out, color_at(x, y), lookup_decode(char_at(x, y)))
43
+ end
44
+ end
45
+ end
46
+ end
@@ -124,8 +124,8 @@ module UnicodePlot
124
124
  left_col = plot.colors_left.fetch(row, :light_black)
125
125
  right_str = plot.labels_right.fetch(row, "")
126
126
  right_col = plot.colors_right.fetch(row, :light_black)
127
- left_len = left_str.length
128
- right_len = right_str.length
127
+ left_len = nocolor_string(left_str).length
128
+ right_len = nocolor_string(right_str).length
129
129
 
130
130
  unless color?(out)
131
131
  left_str = nocolor_string(left_str)
@@ -201,10 +201,10 @@ module UnicodePlot
201
201
 
202
202
  # get length of largest strings to the left and right
203
203
  @max_len_l = plot.show_labels? && !plot.labels_left.empty? ?
204
- plot.labels_left.each_value.map {|l| l.to_s.length }.max :
204
+ plot.labels_left.each_value.map {|l| nocolor_string(l).length }.max :
205
205
  0
206
206
  @max_len_r = plot.show_labels? && !plot.labels_right.empty? ?
207
- plot.labels_right.each_value.map {|l| l.to_s.length }.max :
207
+ plot.labels_right.each_value.map {|l| nocolor_string(l).length }.max :
208
208
  0
209
209
  if plot.show_labels? && plot.ylabel_given?
210
210
  @max_len_l += plot.ylabel_length + 1
@@ -220,7 +220,7 @@ module UnicodePlot
220
220
  @border_padding = " " * @plot_offset
221
221
 
222
222
  # compute position of ylabel
223
- @y_lab_row = (plot.n_rows / 2.0).round
223
+ @y_lab_row = (plot.n_rows / 2.0).round - 1
224
224
  end
225
225
 
226
226
  def print_title(padding, title, p_width: 0, color: :normal)
@@ -0,0 +1,49 @@
1
+ module UnicodePlot
2
+ class Scatterplot < GridPlot
3
+ end
4
+
5
+ module_function def scatterplot(*args,
6
+ canvas: :braille,
7
+ color: :auto,
8
+ name: "",
9
+ **kw)
10
+ case args.length
11
+ when 1
12
+ # y only
13
+ y = Array(args[0])
14
+ x = Array(1 .. y.length)
15
+ when 2
16
+ # x and y
17
+ x = Array(args[0])
18
+ y = Array(args[1])
19
+ else
20
+ raise ArgumentError, "worng number of arguments"
21
+ end
22
+
23
+ plot = Scatterplot.new(x, y, canvas, **kw)
24
+ scatterplot!(plot, x, y, color: color, name: name)
25
+ end
26
+
27
+ module_function def scatterplot!(plot,
28
+ *args,
29
+ color: :auto,
30
+ name: "")
31
+ case args.length
32
+ when 1
33
+ # y only
34
+ y = Array(args[0])
35
+ x = Array(1 .. y.length)
36
+ when 2
37
+ # x and y
38
+ x = Array(args[0])
39
+ y = Array(args[1])
40
+ else
41
+ raise ArgumentError, "worng number of arguments"
42
+ end
43
+
44
+ color = color == :auto ? plot.next_color : color
45
+ plot.annotate!(:r, name.to_s, color: color) unless name.nil? || name == ""
46
+ plot.points!(x, y, color)
47
+ plot
48
+ end
49
+ end
@@ -26,6 +26,16 @@ module UnicodePlot
26
26
  [xmin.to_f, xmax.to_f]
27
27
  end
28
28
 
29
+ def float_round_log10(x, m)
30
+ if x == 0
31
+ 0.0
32
+ elsif x > 0
33
+ x.round(ceil_neg_log10(m) + 1).to_f
34
+ else
35
+ -(-x).round(ceil_neg_log10(m) + 1).to_f
36
+ end
37
+ end
38
+
29
39
  def round_up_subtick(x, m)
30
40
  if x == 0
31
41
  0.0
@@ -54,8 +64,11 @@ module UnicodePlot
54
64
  end
55
65
  end
56
66
 
67
+ INT64_MIN = -9223372036854775808
68
+ INT64_MAX = 9223372036854775807
69
+
57
70
  def roundable?(x)
58
- x.to_i == x
71
+ x.to_i == x && INT64_MIN <= x && x < INT64_MAX
59
72
  end
60
73
  end
61
74
  end
@@ -1,5 +1,5 @@
1
1
  module UnicodePlot
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
 
4
4
  module Version
5
5
  numbers, TAG = VERSION.split("-", 2)
data/lib/unicode_plot.rb CHANGED
@@ -6,11 +6,18 @@ require 'unicode_plot/value_transformer'
6
6
  require 'unicode_plot/renderer'
7
7
 
8
8
  require 'unicode_plot/canvas'
9
- require 'unicode_plot/ascii_canvas'
10
9
  require 'unicode_plot/braille_canvas'
10
+ require 'unicode_plot/density_canvas'
11
+ require 'unicode_plot/lookup_canvas'
12
+ require 'unicode_plot/ascii_canvas'
13
+ require 'unicode_plot/dot_canvas'
11
14
 
12
15
  require 'unicode_plot/plot'
16
+ require 'unicode_plot/grid_plot'
13
17
 
14
18
  require 'unicode_plot/barplot'
15
19
  require 'unicode_plot/boxplot'
20
+ require 'unicode_plot/densityplot'
16
21
  require 'unicode_plot/lineplot'
22
+ require 'unicode_plot/histogram'
23
+ require 'unicode_plot/scatterplot'
data/test/test-barplot.rb CHANGED
@@ -3,30 +3,46 @@ class BarplotTest < Test::Unit::TestCase
3
3
  include Helper::WithTerm
4
4
 
5
5
  sub_test_case("UnicodePlot.barplot") do
6
- sub_test_case("print to tty") do
7
- test("the output is colored") do
8
- data = { bar: 23, foo: 37 }
9
- plot = UnicodePlot.barplot(data: data)
10
- _, output = with_term do
11
- plot.render($stdout)
12
- end
13
- assert_equal(fixture_path("barplot/default.txt").read,
14
- output)
6
+ test("errors") do
7
+ assert_raise(ArgumentError) do
8
+ UnicodePlot.barplot([:a], [-1, 2])
9
+ end
10
+ assert_raise(ArgumentError) do
11
+ UnicodePlot.barplot([:a, :b], [-1, 2])
15
12
  end
16
13
  end
17
14
 
18
- sub_test_case("print to non-tty IO") do
19
- test("the output is not colored") do
20
- data = { bar: 23, foo: 37 }
21
- plot = UnicodePlot.barplot(data: data)
22
- output = StringIO.open do |sio|
23
- sio.print(plot)
24
- sio.close
25
- sio.string
26
- end
27
- assert_equal(fixture_path("barplot/nocolor.txt").read,
28
- output)
15
+ test("colored") do
16
+ data = { bar: 23, foo: 37 }
17
+ plot = UnicodePlot.barplot(data: data)
18
+ _, output = with_term { plot.render($stdout) }
19
+ assert_equal(fixture_path("barplot/default.txt").read,
20
+ output)
21
+
22
+ plot = UnicodePlot.barplot([:bar, :foo], [23, 37])
23
+ _, output = with_term { plot.render($stdout) }
24
+ assert_equal(fixture_path("barplot/default.txt").read,
25
+ output)
26
+ end
27
+
28
+ test("not colored") do
29
+ data = { bar: 23, foo: 37 }
30
+ plot = UnicodePlot.barplot(data: data)
31
+ output = StringIO.open do |sio|
32
+ sio.print(plot)
33
+ sio.close
34
+ sio.string
29
35
  end
36
+ assert_equal(fixture_path("barplot/nocolor.txt").read,
37
+ output)
38
+ end
39
+
40
+ test("mixed") do
41
+ data = { bar: 23.0, 2.1 => 10, foo: 37.0 }
42
+ plot = UnicodePlot.barplot(data: data)
43
+ _, output = with_term { plot.render($stdout) }
44
+ assert_equal(fixture_path("barplot/default_mixed.txt").read,
45
+ output)
30
46
  end
31
47
 
32
48
  sub_test_case("xscale: :log10") do
@@ -55,5 +71,120 @@ class BarplotTest < Test::Unit::TestCase
55
71
  output)
56
72
  end
57
73
  end
74
+
75
+ sub_test_case("with parameters") do
76
+ test("parameters1") do
77
+ plot = UnicodePlot.barplot(
78
+ ["Paris", "New York", "Moskau", "Madrid"],
79
+ [2.244, 8.406, 11.92, 3.165],
80
+ title: "Relative sizes of cities",
81
+ xlabel: "population [in mil]",
82
+ color: :blue,
83
+ margin: 7,
84
+ padding: 3
85
+ )
86
+ _, output = with_term { plot.render($stdout) }
87
+ assert_equal(fixture_path("barplot/parameters1.txt").read,
88
+ output)
89
+ end
90
+
91
+ test("parameters1_nolabels") do
92
+ plot = UnicodePlot.barplot(
93
+ ["Paris", "New York", "Moskau", "Madrid"],
94
+ [2.244, 8.406, 11.92, 3.165],
95
+ title: "Relative sizes of cities",
96
+ xlabel: "population [in mil]",
97
+ color: :blue,
98
+ margin: 7,
99
+ padding: 3,
100
+ labels: false
101
+ )
102
+ _, output = with_term { plot.render($stdout) }
103
+ assert_equal(fixture_path("barplot/parameters1_nolabels.txt").read,
104
+ output)
105
+ end
106
+
107
+ test("parameters2") do
108
+ plot = UnicodePlot.barplot(
109
+ ["Paris", "New York", "Moskau", "Madrid"],
110
+ [2.244, 8.406, 11.92, 3.165],
111
+ title: "Relative sizes of cities",
112
+ xlabel: "population [in mil]",
113
+ color: :yellow,
114
+ border: :solid,
115
+ symbol: "=",
116
+ width: 60
117
+ )
118
+ _, output = with_term { plot.render($stdout) }
119
+ assert_equal(fixture_path("barplot/parameters2.txt").read,
120
+ output)
121
+ end
122
+ end
123
+
124
+ test("ranges") do
125
+ plot = UnicodePlot.barplot(2..6, 11..15)
126
+ _, output = with_term { plot.render($stdout) }
127
+ assert_equal(fixture_path("barplot/ranges.txt").read,
128
+ output)
129
+ end
130
+
131
+ test("all zeros") do
132
+ plot = UnicodePlot.barplot([5, 4, 3, 2, 1], [0, 0, 0, 0, 0])
133
+ _, output = with_term { plot.render($stdout) }
134
+ assert_equal(fixture_path("barplot/edgecase_zeros.txt").read,
135
+ output)
136
+ end
137
+
138
+ test("one large") do
139
+ plot = UnicodePlot.barplot([:a, :b, :c, :d], [1, 1, 1, 1000000])
140
+ _, output = with_term { plot.render($stdout) }
141
+ assert_equal(fixture_path("barplot/edgecase_onelarge.txt").read,
142
+ output)
143
+ end
144
+ end
145
+
146
+ sub_test_case("UnicodePlot.barplot!") do
147
+ test("errors") do
148
+ plot = UnicodePlot.barplot([:bar, :foo], [23, 37])
149
+ assert_raise(ArgumentError) do
150
+ UnicodePlot.barplot!(plot, ["zoom"], [90, 80])
151
+ end
152
+ assert_raise(ArgumentError) do
153
+ UnicodePlot.barplot!(plot, ["zoom", "boom"], [90])
154
+ end
155
+ UnicodePlot.barplot!(plot, "zoom", 90.1)
156
+ end
157
+
158
+ test("return value") do
159
+ plot = UnicodePlot.barplot([:bar, :foo], [23, 37])
160
+ assert_same(plot,
161
+ UnicodePlot.barplot!(plot, ["zoom"], [90]))
162
+ _, output = with_term { plot.render($stdout) }
163
+ assert_equal(fixture_path("barplot/default2.txt").read,
164
+ output)
165
+
166
+ plot = UnicodePlot.barplot([:bar, :foo], [23, 37])
167
+ assert_same(plot,
168
+ UnicodePlot.barplot!(plot, "zoom", 90))
169
+ _, output = with_term { plot.render($stdout) }
170
+ assert_equal(fixture_path("barplot/default2.txt").read,
171
+ output)
172
+
173
+ plot = UnicodePlot.barplot([:bar, :foo], [23, 37])
174
+ assert_same(plot,
175
+ UnicodePlot.barplot!(plot, data: { zoom: 90 }))
176
+ _, output = with_term { plot.render($stdout) }
177
+ assert_equal(fixture_path("barplot/default2.txt").read,
178
+ output)
179
+ end
180
+
181
+ test("ranges") do
182
+ plot = UnicodePlot.barplot(2..6, 11..15)
183
+ assert_same(plot,
184
+ UnicodePlot.barplot!(plot, 9..10, 20..21))
185
+ _, output = with_term { plot.render($stdout) }
186
+ assert_equal(fixture_path("barplot/ranges2.txt").read,
187
+ output)
188
+ end
58
189
  end
59
190
  end