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 +4 -4
- data/README.md +3 -0
- data/lib/unicode_plot/ascii_canvas.rb +2 -39
- data/lib/unicode_plot/barplot.rb +46 -2
- data/lib/unicode_plot/braille_canvas.rb +0 -1
- data/lib/unicode_plot/canvas.rb +12 -3
- data/lib/unicode_plot/density_canvas.rb +56 -0
- data/lib/unicode_plot/densityplot.rb +10 -0
- data/lib/unicode_plot/dot_canvas.rb +34 -0
- data/lib/unicode_plot/grid_plot.rb +101 -0
- data/lib/unicode_plot/histogram.rb +48 -0
- data/lib/unicode_plot/lineplot.rb +1 -105
- data/lib/unicode_plot/lookup_canvas.rb +46 -0
- data/lib/unicode_plot/renderer.rb +5 -5
- data/lib/unicode_plot/scatterplot.rb +49 -0
- data/lib/unicode_plot/utils.rb +14 -1
- data/lib/unicode_plot/version.rb +1 -1
- data/lib/unicode_plot.rb +8 -1
- data/test/test-barplot.rb +151 -20
- data/test/test-canvas.rb +15 -1
- data/test/test-densityplot.rb +38 -0
- data/test/test-histogram.rb +110 -0
- data/test/test-lineplot.rb +67 -0
- data/test/test-scatterplot.rb +146 -0
- metadata +15 -2
@@ -1,111 +1,7 @@
|
|
1
1
|
require 'date'
|
2
2
|
|
3
3
|
module UnicodePlot
|
4
|
-
class
|
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.
|
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.
|
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
|
data/lib/unicode_plot/utils.rb
CHANGED
@@ -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
|
data/lib/unicode_plot/version.rb
CHANGED
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|