Skip to content

Commit d737252

Browse files
committed
Refactor
1 parent 06d6160 commit d737252

5 files changed

Lines changed: 90 additions & 15 deletions

File tree

spec/reference_spec.cr

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,18 @@ describe "Julia reference output compatibility" do
860860
end
861861

862862
describe "heatmap" do
863+
it "matches heatmap/default_0x0" do
864+
z = Array(Array(Float64)).new(0) { Array(Float64).new(0, 0.0) }
865+
p = UnicodePlot.heatmap(z, labels: false)
866+
test_ref("heatmap/default_0x0.txt", p)
867+
end
868+
869+
it "matches heatmap/default_5x0" do
870+
z = Array.new(5) { [] of Float64 }
871+
p = UnicodePlot.heatmap(z, labels: false)
872+
test_ref("heatmap/default_5x0.txt", p)
873+
end
874+
863875
# integers_20x20: repeat(collect(1:20), outer=(1,20)) → A[i,j] = i
864876
it "matches heatmap/integers_20x20" do
865877
z = Array.new(20) { |i| Array.new(20) { |_j| (i + 1).to_f } }
@@ -1140,6 +1152,12 @@ describe "Julia reference output compatibility" do
11401152
end
11411153

11421154
describe "sizing" do
1155+
it "matches spy/default_0x0" do
1156+
a = Array(Array(Float64)).new(0) { Array(Float64).new(0, 0.0) }
1157+
p = UnicodePlot.spy(a)
1158+
test_ref("spy/default_0x0.txt", p)
1159+
end
1160+
11431161
it "matches spy/default_10x10" do
11441162
p = UnicodePlot.spy(spy_fixture_matrix("10x10"))
11451163
test_ref("spy/default_10x10.txt", p)

spec/unicode_plot_spec.cr

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,9 @@ describe UnicodePlot do
470470
p.xlabel.should contain("⩵ 0")
471471
end
472472

473-
it "rejects empty matrices" do
474-
expect_raises(ArgumentError, /must not be empty/) do
475-
UnicodePlot.spy([] of Array(Float64))
476-
end
473+
it "accepts empty matrices" do
474+
p = UnicodePlot.spy([] of Array(Float64))
475+
p.should be_a(UnicodePlot::Plot)
477476
end
478477
end
479478

src/unicode_plot/interface/heatmap.cr

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ z : Array(Array(Float64)),
134134
fix_ar : Bool = false,
135135
out_stream : IO? = nil,) : Plot
136136
matrix = MatrixView.new(z)
137-
raise ArgumentError.new("z must not be empty") if matrix.empty?
138137

139138
data_nrows = matrix.nrows
140139
data_ncols = matrix.ncols
@@ -145,12 +144,12 @@ z : Array(Array(Float64)),
145144

146145
# autolims: {0,0} means auto → use full data range.
147146
eff_ylim = if ylim == {0.0, 0.0}
148-
y_arr.empty? ? {0.0, 1.0} : {y_arr.min, y_arr.max}
147+
y_arr.empty? ? {0.0, 0.0} : {y_arr.min, y_arr.max}
149148
else
150149
{ylim[0].to_f, ylim[1].to_f}
151150
end
152151
eff_xlim = if xlim == {0.0, 0.0}
153-
x_arr.empty? ? {0.0, 1.0} : {x_arr.min, x_arr.max}
152+
x_arr.empty? ? {0.0, 0.0} : {x_arr.min, x_arr.max}
154153
else
155154
{xlim[0].to_f, xlim[1].to_f}
156155
end

src/unicode_plot/interface/spy.cr

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ module UnicodePlot
3131
fix_ar : Bool,
3232
out_stream : IO? = nil,
3333
) : {Int32, Int32}
34+
return {0, 0} if nrows == 0 && ncols == 0
35+
3436
yppc, xppc = spy_canvas_pixel_per_char(canvas)
3537
canv_height = nrows.to_f64 / yppc
3638
canv_width = ncols.to_f64 / xppc
@@ -82,6 +84,7 @@ module UnicodePlot
8284

8385
{final_h.clamp(1, Int32::MAX), final_w.clamp(1, Int32::MAX)}
8486
end
87+
8588
# ameba:enable Metrics/CyclomaticComplexity
8689

8790
def spy(
@@ -111,7 +114,6 @@ module UnicodePlot
111114
thousands_separator : Char = ' ',
112115
) : Plot
113116
matrix = MatrixView.new(a)
114-
raise ArgumentError.new("a must not be empty") if matrix.empty?
115117

116118
nrows = matrix.nrows
117119
ncols = matrix.ncols
@@ -162,8 +164,9 @@ module UnicodePlot
162164
grid: false,
163165
# Julia's spy passes canvas_kw: (height: 1 + nrow, width: 1 + ncol).
164166
# build_plot has no canvas_kw, so use widened limits to match the same mapping scale.
165-
xlim: {1.0, ncols.to_f64 + 2.0},
166-
ylim: {1.0, nrows.to_f64 + 2.0},
167+
# For 0x0 matrices, Julia uses xlim=[1,0]/ylim=[1,0] (inverted); Crystal maps this to {0.0,1.0}.
168+
xlim: ncols == 0 ? {0.0, 1.0} : {1.0, ncols.to_f64 + 2.0},
169+
ylim: nrows == 0 ? {0.0, 1.0} : {1.0, nrows.to_f64 + 2.0},
167170
xflip: xflip,
168171
yflip: yflip,
169172
unicode_exponent: unicode_exponent,
@@ -191,12 +194,26 @@ module UnicodePlot
191194
plot.xlabel = "#{plot.nice_repr(vals.size)} #{show_zeros ? "⩵ 0" : "≠ 0"}"
192195
end
193196

194-
# Keep displayed axis endpoints identical to Julia's spy (1..ncol, 1..nrow).
197+
# Keep displayed axis endpoints identical to Julia's spy.
195198
bc = ansi_color(border_color)
196-
plot.label!(:bl, xflip ? nice_repr(ncols, unicode_exponent, thousands_separator) : "1", bc)
197-
plot.label!(:br, xflip ? "1" : nice_repr(ncols, unicode_exponent, thousands_separator), bc)
198-
plot.label!(:l, plot.canvas.nrows, yflip ? nice_repr(nrows, unicode_exponent, thousands_separator) : "1", bc)
199-
plot.label!(:l, 1, yflip ? "1" : nice_repr(nrows, unicode_exponent, thousands_separator), bc)
199+
x_left, x_right = matrix_horizontal_axis_labels(
200+
ncols,
201+
flip: xflip,
202+
unicode_exponent: unicode_exponent,
203+
thousands_separator: thousands_separator,
204+
zero_fallback_range: true,
205+
)
206+
y_top, y_bottom = matrix_vertical_axis_labels(
207+
nrows,
208+
flip: yflip,
209+
unicode_exponent: unicode_exponent,
210+
thousands_separator: thousands_separator,
211+
zero_fallback_range: true,
212+
)
213+
plot.label!(:bl, x_left, bc)
214+
plot.label!(:br, x_right, bc)
215+
plot.label!(:l, plot.canvas.nrows, y_top, bc)
216+
plot.label!(:l, 1, y_bottom, bc)
200217

201218
plot
202219
end

src/unicode_plot/matrix.cr

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,48 @@ module UnicodePlot
4747
end
4848
end
4949

50+
# Returns {low_label, high_label} for matrix index coordinates.
51+
# Standard matrix indexing is 1..n; for 0-sized axes in Julia compatibility mode
52+
# we use 0..1 to reflect the plotted fallback range.
53+
def matrix_index_range_labels(
54+
size : Int32,
55+
unicode_exponent : Bool = true,
56+
thousands_separator : Char = ' ',
57+
zero_fallback_range : Bool = true,
58+
) : {String, String}
59+
if size == 0
60+
zero_fallback_range ? {"0", "1"} : {"1", "0"}
61+
else
62+
{"1", nice_repr(size, unicode_exponent, thousands_separator)}
63+
end
64+
end
65+
66+
# Returns {left_label, right_label} for horizontal axis labels.
67+
def matrix_horizontal_axis_labels(
68+
size : Int32,
69+
*,
70+
flip : Bool,
71+
unicode_exponent : Bool = true,
72+
thousands_separator : Char = ' ',
73+
zero_fallback_range : Bool = true,
74+
) : {String, String}
75+
lo, hi = matrix_index_range_labels(size, unicode_exponent, thousands_separator, zero_fallback_range)
76+
flip ? {hi, lo} : {lo, hi}
77+
end
78+
79+
# Returns {top_label, bottom_label} for vertical axis labels.
80+
def matrix_vertical_axis_labels(
81+
size : Int32,
82+
*,
83+
flip : Bool,
84+
unicode_exponent : Bool = true,
85+
thousands_separator : Char = ' ',
86+
zero_fallback_range : Bool = true,
87+
) : {String, String}
88+
lo, hi = matrix_index_range_labels(size, unicode_exponent, thousands_separator, zero_fallback_range)
89+
flip ? {hi, lo} : {lo, hi}
90+
end
91+
5092
def matrix_extrema_finite(matrix : MatrixView(T)) : {Float64, Float64}? forall T
5193
{% unless T <= Number %}
5294
{% raise "matrix_extrema_finite requires numeric matrix elements" %}

0 commit comments

Comments
 (0)