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 +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
|