Skip to content

Commit 2ab9c78

Browse files
committed
multi-series API
1 parent 7da7b79 commit 2ab9c78

5 files changed

Lines changed: 303 additions & 11 deletions

File tree

spec/reference_spec.cr

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,13 @@ describe "Julia reference output compatibility" do
251251
end
252252

253253
it "matches lineplot/color_vector" do
254-
p = UnicodePlot.lineplot([Float64::NAN], [Float64::NAN], xlim: {-1.0, 7.0}, ylim: {-1.0, 9.0})
255-
UnicodePlot.lineplot!(p, [-1.0, 2.0], [-1.0, 2.0], color: :red)
256-
UnicodePlot.lineplot!(p, [2.0, 3.0], [2.0, 9.0], color: :green)
257-
UnicodePlot.lineplot!(p, [3.0, 7.0], [9.0, 4.0], color: :blue)
254+
p = UnicodePlot.lineplot(
255+
[[-1.0, 2.0], [2.0, 3.0], [3.0, 7.0]],
256+
[[-1.0, 2.0], [2.0, 9.0], [9.0, 4.0]],
257+
color: [:red, :green, :blue],
258+
xlim: {-1.0, 7.0},
259+
ylim: {-1.0, 9.0},
260+
)
258261
test_ref("lineplot/color_vector.txt", p)
259262
end
260263

spec/unicode_plot_spec.cr

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,25 @@ describe UnicodePlot do
6060
p.to_s.should be_a(String)
6161
end
6262

63+
it "accepts multi-series input with per-series colors" do
64+
p = UnicodePlot.lineplot(
65+
[[-1.0, 2.0], [2.0, 3.0], [3.0, 7.0]],
66+
[[-1.0, 2.0], [2.0, 9.0], [9.0, 4.0]],
67+
color: [:red, :green, :blue]
68+
)
69+
p.series.should eq(3)
70+
end
71+
72+
it "raises when color vector length does not match series count" do
73+
expect_raises(ArgumentError, /color vector must have the same length as the number of series/) do
74+
UnicodePlot.lineplot(
75+
[[1.0, 2.0], [3.0, 4.0]],
76+
[[1.0, 2.0], [3.0, 4.0]],
77+
color: [:red]
78+
)
79+
end
80+
end
81+
6382
it "accepts Int64 arrays via numeric overload" do
6483
p = UnicodePlot.lineplot([1_i64, 2_i64, 3_i64], [1_i64, 4_i64, 9_i64])
6584
p.to_s.should be_a(String)
@@ -140,6 +159,15 @@ describe UnicodePlot do
140159
p.to_s.should be_a(String)
141160
end
142161

162+
it "accepts multi-series input with per-series colors" do
163+
p = UnicodePlot.scatterplot(
164+
[[1.0, 2.0], [2.0, 3.0]],
165+
[[1.0, 4.0], [4.0, 9.0]],
166+
color: [:red, :green]
167+
)
168+
p.series.should eq(2)
169+
end
170+
143171
it "accepts y-only numeric overload" do
144172
p = UnicodePlot.scatterplot([1_i16, 4_i16, 9_i16])
145173
p.to_s.should be_a(String)

src/unicode_plot/common.cr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ module UnicodePlot
66
HALF_BLOCK = '▄'
77

88
alias ColorType = UInt32
9+
alias PlotColor = Symbol | UInt32 | {Int32, Int32, Int32}
10+
alias PlotColorArg = PlotColor | Array(Symbol) | Array(UInt32) | Array({Int32, Int32, Int32})
911
INVALID_COLOR = UInt32::MAX
1012
THRESHOLD = 256_u32 ** 3 # 16_777_216 — 8bit/24bit threshold
1113

src/unicode_plot/interface/lineplot.cr

Lines changed: 135 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module UnicodePlot
55
*,
66
canvas : Symbol = :braille,
77
name : String = "",
8-
color : Symbol | UInt32 | {Int32, Int32, Int32} = :auto,
8+
color : PlotColor = :auto,
99
head_tail : Symbol? = nil,
1010
head_tail_frac : Float64 = 0.05,
1111
title : String = "",
@@ -35,8 +35,90 @@ module UnicodePlot
3535
thousands_separator : Char = ' ',
3636
) : Plot
3737
raise ArgumentError.new("x and y must have the same length") unless x.size == y.size
38+
lineplot(
39+
[x], [y],
40+
canvas: canvas,
41+
name: name,
42+
color: color,
43+
head_tail: head_tail,
44+
head_tail_frac: head_tail_frac,
45+
title: title,
46+
xlabel: xlabel,
47+
ylabel: ylabel,
48+
xscale: xscale,
49+
yscale: yscale,
50+
height: height,
51+
width: width,
52+
border: border,
53+
compact_labels: compact_labels,
54+
compact: compact,
55+
blend: blend,
56+
xlim: xlim,
57+
ylim: ylim,
58+
margin: margin,
59+
padding: padding,
60+
labels: labels,
61+
grid: grid,
62+
yticks: yticks,
63+
xticks: xticks,
64+
min_height: min_height,
65+
min_width: min_width,
66+
yflip: yflip,
67+
xflip: xflip,
68+
unicode_exponent: unicode_exponent,
69+
thousands_separator: thousands_separator,
70+
)
71+
end
72+
73+
def lineplot(
74+
x : Array(Array(Float64)),
75+
y : Array(Array(Float64)),
76+
*,
77+
canvas : Symbol = :braille,
78+
name : String = "",
79+
color : PlotColorArg = :auto,
80+
head_tail : Symbol? = nil,
81+
head_tail_frac : Float64 = 0.05,
82+
title : String = "",
83+
xlabel : String = "",
84+
ylabel : String = "",
85+
xscale : Symbol | Proc(Float64, Float64) = :identity,
86+
yscale : Symbol | Proc(Float64, Float64) = :identity,
87+
height : Int32? = nil,
88+
width : Int32? = nil,
89+
border : Symbol = :solid,
90+
compact_labels : Bool = false,
91+
compact : Bool = false,
92+
blend : Bool = true,
93+
xlim : {Float64, Float64} = {0.0, 0.0},
94+
ylim : {Float64, Float64} = {0.0, 0.0},
95+
margin : Int32 = 3,
96+
padding : Int32 = 1,
97+
labels : Bool = true,
98+
grid : Bool = true,
99+
yticks : Bool = true,
100+
xticks : Bool = true,
101+
min_height : Int32 = 2,
102+
min_width : Int32 = 5,
103+
yflip : Bool = false,
104+
xflip : Bool = false,
105+
unicode_exponent : Bool = true,
106+
thousands_separator : Char = ' ',
107+
) : Plot
108+
raise ArgumentError.new("x and y must have the same number of series") unless x.size == y.size
109+
if color.is_a?(Array)
110+
raise ArgumentError.new("color vector must have the same length as the number of series") unless color.size == x.size
111+
end
112+
113+
x.each_with_index do |x_values, i|
114+
yv = y[i]
115+
raise ArgumentError.new("x and y series must have the same length") unless x_values.size == yv.size
116+
end
117+
118+
flat_x = x.flatten
119+
flat_y = y.flatten
38120
plot = build_plot(
39-
x, y,
121+
flat_x, flat_y,
40122
canvas_type: canvas,
41123
title: title, xlabel: xlabel, ylabel: ylabel,
42124
xscale: xscale, yscale: yscale,
@@ -89,18 +171,61 @@ module UnicodePlot
89171
lineplot(to_plot_f64(x), to_plot_f64(y), **kwargs)
90172
end
91173

92-
# ameba:disable Metrics/CyclomaticComplexity
174+
def lineplot(x : Array(Array(T)), y : Array(Array(U)), **kwargs) : Plot forall T, U
175+
xs = x.map { |vals| to_plot_f64(vals) }
176+
ys = y.map { |vals| to_plot_f64(vals) }
177+
lineplot(xs, ys, **kwargs)
178+
end
179+
93180
def lineplot!(
94181
plot : Plot,
95182
x : Array(Float64),
96183
y : Array(Float64),
97184
*,
98185
name : String = "",
99-
color : Symbol | UInt32 | {Int32, Int32, Int32} = :auto,
186+
color : PlotColor = :auto,
100187
head_tail : Symbol? = nil,
101188
head_tail_frac : Float64 = 0.05,
102189
) : Plot
103190
raise ArgumentError.new("x and y must have the same length") unless x.size == y.size
191+
lineplot!(plot, [x], [y], name: name, color: color, head_tail: head_tail, head_tail_frac: head_tail_frac)
192+
end
193+
194+
def lineplot!(
195+
plot : Plot,
196+
x : Array(Array(Float64)),
197+
y : Array(Array(Float64)),
198+
*,
199+
name : String = "",
200+
color : PlotColorArg = :auto,
201+
head_tail : Symbol? = nil,
202+
head_tail_frac : Float64 = 0.05,
203+
) : Plot
204+
raise ArgumentError.new("x and y must have the same number of series") unless x.size == y.size
205+
if color.is_a?(Array)
206+
raise ArgumentError.new("color vector must have the same length as the number of series") unless color.size == x.size
207+
end
208+
209+
x.each_with_index do |x_values, i|
210+
yv = y[i]
211+
raise ArgumentError.new("x and y series must have the same length") unless x_values.size == yv.size
212+
213+
series_color = color.is_a?(Array) ? color[i] : color
214+
lineplot_single!(plot, x_values, yv, name: name, color: series_color, head_tail: head_tail, head_tail_frac: head_tail_frac)
215+
end
216+
plot
217+
end
218+
219+
private def lineplot_single!(
220+
plot : Plot,
221+
x : Array(Float64),
222+
y : Array(Float64),
223+
*,
224+
name : String = "",
225+
color : PlotColor = :auto,
226+
head_tail : Symbol? = nil,
227+
head_tail_frac : Float64 = 0.05,
228+
) : Plot
104229
c = color == :auto ? plot.next_color! : color
105230
col = plot_color(c)
106231
plot.label!(:r, name, col) unless name.empty?
@@ -137,6 +262,12 @@ module UnicodePlot
137262
lineplot!(plot, to_plot_f64(x), to_plot_f64(y), **kwargs)
138263
end
139264

265+
def lineplot!(plot : Plot, x : Array(Array(T)), y : Array(Array(U)), **kwargs) : Plot forall T, U
266+
xs = x.map { |vals| to_plot_f64(vals) }
267+
ys = y.map { |vals| to_plot_f64(vals) }
268+
lineplot!(plot, xs, ys, **kwargs)
269+
end
270+
140271
def lineplot!(plot : Plot, y : Array(T), **kwargs) : Plot forall T
141272
x = (1..y.size).map(&.to_f64)
142273
lineplot!(plot, x, to_plot_f64(y), **kwargs)

0 commit comments

Comments
 (0)