Skip to content

Commit 6c1e539

Browse files
committed
Add densityplot support
1 parent 16c2954 commit 6c1e539

8 files changed

Lines changed: 154 additions & 4 deletions

File tree

lib/unicode_plot.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
require 'unicode_plot/canvas'
99
require 'unicode_plot/ascii_canvas'
1010
require 'unicode_plot/braille_canvas'
11+
require 'unicode_plot/density_canvas'
1112

1213
require 'unicode_plot/plot'
1314
require 'unicode_plot/grid_plot'
1415

1516
require 'unicode_plot/barplot'
1617
require 'unicode_plot/boxplot'
18+
require 'unicode_plot/densityplot'
1719
require 'unicode_plot/lineplot'
1820
require 'unicode_plot/histogram'
1921
require 'unicode_plot/scatterplot'

lib/unicode_plot/braille_canvas.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ def pixel!(pixel_x, pixel_y, color)
5353

5454
def print_row(out, row_index)
5555
unless 0 <= row_index && row_index < height
56-
$stderr.puts [row_index, height].inspect
5756
raise ArgumentError, "row_index out of bounds"
5857
end
5958
y = row_index

lib/unicode_plot/canvas.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ def self.create(canvas_type, width, height, **kw)
88
AsciiCanvas.new(width, height, **kw)
99
when :braille
1010
BrailleCanvas.new(width, height, **kw)
11+
when :density
12+
DensityCanvas.new(width, height, **kw)
1113
else
1214
raise ArgumentError, "unknown canvas type: #{canvas_type}"
1315
end
@@ -22,8 +24,8 @@ def initialize(width, height, pixel_width, pixel_height, fill_char,
2224
y_pixel_per_char: 1)
2325
@width = width
2426
@height = height
25-
@pixel_width = pixel_width
26-
@pixel_height = pixel_height
27+
@pixel_width = check_positive(pixel_width, :pixel_width)
28+
@pixel_height = check_positive(pixel_height, :pixel_height)
2729
@origin_x = origin_x
2830
@origin_y = origin_y
2931
@plot_width = plot_width
@@ -83,7 +85,7 @@ def index_at(x, y)
8385
def point!(x, y, color)
8486
unless origin_x <= x && x <= origin_x + plot_width &&
8587
origin_y <= y && y <= origin_y + plot_height
86-
return c
88+
return color
8789
end
8890

8991
plot_offset_x = x - origin_x
@@ -156,5 +158,10 @@ def lines!(x, y, color = :normal)
156158
line!(x[i], y[i], x[i+1], y[i+1], color)
157159
end
158160
end
161+
162+
private def check_positive(value, name)
163+
return value if value > 0
164+
raise ArgumentError, "#{name} has to be positive"
165+
end
159166
end
160167
end

lib/unicode_plot/density_canvas.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

lib/unicode_plot/densityplot.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
 ┌────────────────────────────────────────┐
2+
5 │                                        │
3+
 │                             ░░         │
4+
 │                        ░░░ ░ ░░  ░░    │
5+
 │                   ░ ░░░░░░░▒▒░▒░░░▒    │
6+
 │                  ░░░▒▒░▒▓▒▒▒▒▒░░▓░░░ ░ │
7+
 │           ░   ░░░░▒░▒░▒▒▒▒▓▒▓▓▓▒▓▒░░░░░│
8+
 │         ░ ░░░░░░░░▒▒░▒▒▓▓▒▒▓▓▓▓▒▓░░░░░ │
9+
 │         ░░░░░▒░░▒░▓▒░▒▒▒▓░▒▒▒▓▒░▒▒ ▒   │
10+
 │        ░░▒▒░▒▒█▓▓▒▒▒░▓▒░▒▒▒▒ ░░░░ ░    │
11+
 │       ░░░░░▒▒▓▓▒▒▓█▓▓█▒░▒▒░ ░░   ░     │
12+
 │       ░░░░▒▒▒▒▒▒▒▓▒▒▒▒░░░░░            │
13+
 │        ░  ░░▒░▒░▒▒▒░▒▒░░               │
14+
 │         ░ ░░░░░░ ░░  ░                 │
15+
 │                                        │
16+
-3 │                                        │
17+
 └────────────────────────────────────────┘
18+
 -3  4
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
 Title
2+
 ┌────────────────────────────────────────┐
3+
5 │                                        │ foo
4+
 │                             ░░         │ bar
5+
 │                        ░░░ ░ ░░  ░░    │
6+
 │                   ░ ░░░░░░░▒▒░▒░░░▒    │
7+
 │                  ░░░▒▒░▒▓▒▒▒▒▒░░▓░░░ ░ │
8+
 │           ░   ░░░░▒░▒░▒▒▒▒▓▒▓▓▓▒▓▒░░░░░│
9+
 │         ░ ░░░░░░░░▒▒░▒▒▓▓▒▒▓▓▓▓▒▓░░░░░ │
10+
 │         ░░░░░▒░░▒░▓▒░▒▒▒▓░▒▒▒▓▒░▒▒ ▒   │
11+
 │        ░░▒▒░▒▒█▓▓▒▒▒░▓▒░▒▒▒▒ ░░░░ ░    │
12+
 │       ░░░░░▒▒▓▓▒▒▓█▓▓█▒░▒▒░ ░░   ░     │
13+
 │       ░░░░▒▒▒▒▒▒▒▓▒▒▒▒░░░░░            │
14+
 │        ░  ░░▒░▒░▒▒▒░▒▒░░               │
15+
 │         ░ ░░░░░░ ░░  ░                 │
16+
 │                                        │
17+
-3 │                                        │
18+
 └────────────────────────────────────────┘
19+
 -3  4
20+
 x

test/test-densityplot.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class DensityplotTest < Test::Unit::TestCase
2+
include Helper::Fixture
3+
include Helper::WithTerm
4+
5+
def setup
6+
randn = fixture_path("randn_1338_2000.txt").read.each_line.map(&:to_f)
7+
@dx = randn[0, 1000].compact
8+
@dy = randn[1000, 1000].compact
9+
assert_equal(1000, @dx.length)
10+
assert_equal(1000, @dy.length)
11+
end
12+
13+
test("default") do
14+
plot = UnicodePlot.densityplot(@dx, @dy)
15+
dx2 = @dx.map {|x| x + 2 }
16+
dy2 = @dy.map {|y| y + 2 }
17+
assert_same(plot,
18+
UnicodePlot.densityplot!(plot, dx2, dy2))
19+
_, output = with_term { plot.render($stdout) }
20+
expected = fixture_path("scatterplot/densityplot.txt").read
21+
assert_equal(output, expected)
22+
end
23+
24+
test("parameters") do
25+
plot = UnicodePlot.densityplot(@dx, @dy,
26+
name: "foo",
27+
color: :red,
28+
title: "Title",
29+
xlabel: "x")
30+
dx2 = @dx.map {|x| x + 2 }
31+
dy2 = @dy.map {|y| y + 2 }
32+
assert_same(plot,
33+
UnicodePlot.densityplot!(plot, dx2, dy2, name: "bar"))
34+
_, output = with_term { plot.render($stdout) }
35+
expected = fixture_path("scatterplot/densityplot_parameters.txt").read
36+
assert_equal(output, expected)
37+
end
38+
end

0 commit comments

Comments
 (0)