unicode_plot 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/README.md +10 -12
- data/Rakefile +4 -1
- data/lib/unicode_plot.rb +3 -5
- data/lib/unicode_plot/barplot.rb +67 -2
- data/lib/unicode_plot/canvas.rb +18 -9
- data/lib/unicode_plot/{ascii_canvas.rb → canvas/ascii_canvas.rb} +4 -0
- data/lib/unicode_plot/canvas/block_canvas.rb +38 -0
- data/lib/unicode_plot/{braille_canvas.rb → canvas/braille_canvas.rb} +3 -1
- data/lib/unicode_plot/{density_canvas.rb → canvas/density_canvas.rb} +2 -0
- data/lib/unicode_plot/{dot_canvas.rb → canvas/dot_canvas.rb} +2 -0
- data/lib/unicode_plot/{lookup_canvas.rb → canvas/lookup_canvas.rb} +1 -1
- data/lib/unicode_plot/io_context.rb +32 -0
- data/lib/unicode_plot/lineplot.rb +36 -0
- data/lib/unicode_plot/plot.rb +8 -3
- data/lib/unicode_plot/renderer.rb +5 -1
- data/lib/unicode_plot/stairs.rb +88 -0
- data/lib/unicode_plot/stemplot.rb +344 -0
- data/lib/unicode_plot/styled_printer.rb +1 -5
- data/lib/unicode_plot/version.rb +1 -1
- data/test/helper/with_term.rb +16 -10
- data/test/test-barplot.rb +8 -0
- data/test/test-boxplot.rb +8 -0
- data/test/test-canvas.rb +15 -8
- data/test/test-densityplot.rb +8 -0
- data/test/test-histogram.rb +8 -0
- data/test/test-lineplot.rb +85 -1
- data/test/test-plot.rb +16 -0
- data/test/test-scatterplot.rb +8 -0
- data/test/test-stemplot.rb +95 -0
- data/test/test-utils.rb +13 -0
- data/unicode_plot.gemspec +4 -0
- metadata +40 -20
- data/lib/unicode_plot/layout.rb +0 -51
- data/test/test-result.rb +0 -0
@@ -40,6 +40,10 @@ module UnicodePlot
|
|
40
40
|
barplot: BorderMaps::BORDER_BARPLOT,
|
41
41
|
}.freeze
|
42
42
|
|
43
|
+
def self.border_types
|
44
|
+
BORDER_MAP.keys
|
45
|
+
end
|
46
|
+
|
43
47
|
module BorderPrinter
|
44
48
|
include StyledPrinter
|
45
49
|
|
@@ -128,7 +132,7 @@ module UnicodePlot
|
|
128
132
|
left_len = nocolor_string(left_str).length
|
129
133
|
right_len = nocolor_string(right_str).length
|
130
134
|
|
131
|
-
unless color?
|
135
|
+
unless out.color?
|
132
136
|
left_str = nocolor_string(left_str)
|
133
137
|
right_str = nocolor_string(right_str)
|
134
138
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module UnicodePlot
|
3
|
+
# @overload stairs(x, y, style: :post, name: "", title: "", xlabel: "", ylabel: "", labels: true, border: :solid, margin: 3, padding: 1, color: :auto, width: 40, height: 15, xlim: [0, 0], ylim: [0, 0], canvas: :braille, grid: true)
|
4
|
+
#
|
5
|
+
# Draws a staircase plot on a new canvas.
|
6
|
+
#
|
7
|
+
# The first vector `x` should contain the horizontal
|
8
|
+
# positions for all the points. The second vector `y` should then
|
9
|
+
# contain the corresponding vertical positions respectively. This
|
10
|
+
# means that the two vectors must be of the same length and
|
11
|
+
# ordering.
|
12
|
+
#
|
13
|
+
# @param x [Array<Numeric>] The horizontal position for each point.
|
14
|
+
# @param y [Array<Numeric>] The vertical position for each point.
|
15
|
+
# @param style [Symbol] Specifies where the transition of the stair takes place. Can be either `:pre` or `:post`.
|
16
|
+
# @param name [String] Annotation of the current drawing to be displayed on the right.
|
17
|
+
# @param height [Integer] Number of character rows that should be used for plotting.
|
18
|
+
# @param xlim [Array<Numeric>] Plotting range for the x axis. `[0, 0]` stands for automatic.
|
19
|
+
# @param ylim [Array<Numeric>] Plotting range for the y axis. `[0, 0]` stands for automatic.
|
20
|
+
# @param canvas [Symbol] The type of canvas that should be used for drawing.
|
21
|
+
# @param grid [Boolean] If `true`, draws grid-lines at the origin.
|
22
|
+
#
|
23
|
+
# @return [Plot] A plot object.
|
24
|
+
#
|
25
|
+
# @example Example usage of stairs on IRB:
|
26
|
+
#
|
27
|
+
# >> UnicodePlot.stairs([1, 2, 4, 7, 8], [1, 3, 4, 2, 7], style: :post, title: "My Staircase Plot").render
|
28
|
+
# My Staircase Plot
|
29
|
+
# ┌────────────────────────────────────────┐
|
30
|
+
# 7 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
|
31
|
+
# │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
|
32
|
+
# │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
|
33
|
+
# │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
|
34
|
+
# │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
|
35
|
+
# │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
|
36
|
+
# │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
|
37
|
+
# │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⡄⠀⠀⠀⠀⢸│
|
38
|
+
# │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢸│
|
39
|
+
# │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢸│
|
40
|
+
# │⠀⠀⠀⠀⠀⢸⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢸│
|
41
|
+
# │⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢸│
|
42
|
+
# │⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠧⠤⠤⠤⠤⠼│
|
43
|
+
# │⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
|
44
|
+
# 1 │⣀⣀⣀⣀⣀⣸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
|
45
|
+
# └────────────────────────────────────────┘
|
46
|
+
# 1 8
|
47
|
+
# => nil
|
48
|
+
#
|
49
|
+
# @see Plot
|
50
|
+
# @see scatterplot
|
51
|
+
# @see lineplot
|
52
|
+
module_function def stairs(xvec, yvec, style: :post, **kw)
|
53
|
+
x_vex, y_vex = compute_stair_lines(xvec, yvec, style: style)
|
54
|
+
lineplot(x_vex, y_vex, **kw)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Similar to stairs, but takes an existing plot object as a first argument.
|
58
|
+
module_function def stairs!(plot, xvec, yvec, style: :post, **kw)
|
59
|
+
x_vex, y_vex = compute_stair_lines(xvec, yvec, style: style)
|
60
|
+
lineplot!(plot, x_vex, y_vex, **kw)
|
61
|
+
end
|
62
|
+
|
63
|
+
module_function def compute_stair_lines(x, y, style: :post)
|
64
|
+
x_vex = Array.new(x.length * 2 - 1, 0)
|
65
|
+
y_vex = Array.new(x.length * 2 - 1, 0)
|
66
|
+
x_vex[0] = x[0]
|
67
|
+
y_vex[0] = y[0]
|
68
|
+
o = 0
|
69
|
+
if style == :post
|
70
|
+
(1 ... x.length).each do |i|
|
71
|
+
x_vex[i + o] = x[i]
|
72
|
+
x_vex[i + o + 1] = x[i]
|
73
|
+
y_vex[i + o] = y[i-1]
|
74
|
+
y_vex[i + o + 1] = y[i]
|
75
|
+
o += 1
|
76
|
+
end
|
77
|
+
elsif style == :pre
|
78
|
+
(1 ... x.length).each do |i|
|
79
|
+
x_vex[i + o] = x[i-1]
|
80
|
+
x_vex[i + o + 1] = x[i]
|
81
|
+
y_vex[i + o] = y[i]
|
82
|
+
y_vex[i + o + 1] = y[i]
|
83
|
+
o += 1
|
84
|
+
end
|
85
|
+
end
|
86
|
+
return [x_vex, y_vex]
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,344 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module UnicodePlot
|
4
|
+
|
5
|
+
# ## Description
|
6
|
+
#
|
7
|
+
# Draw a stem-leaf plot of the given vector +vec+.
|
8
|
+
#
|
9
|
+
# ```
|
10
|
+
# stemplot(vec, **kwargs)
|
11
|
+
# ```
|
12
|
+
#
|
13
|
+
# Draw a back-to-back stem-leaf plot of the given vectors +vec1+ and +vec2+.
|
14
|
+
#
|
15
|
+
# ```
|
16
|
+
# stemplot(vec1, vec2, **kwargs)
|
17
|
+
# ```
|
18
|
+
#
|
19
|
+
# The vectors can be any object that converts to an Array, e.g. an Array, Range, etc.
|
20
|
+
# If all elements of the vector are Numeric, the stem-leaf plot is classified as a
|
21
|
+
# {NumericStemplot}, otherwise it is classified as a {StringStemplot}. Back-to-back
|
22
|
+
# stem-leaf plots must be the same type, i.e. String and Numeric stem-leaf plots cannot
|
23
|
+
# be mixed in a back-to-back plot.
|
24
|
+
#
|
25
|
+
# ## Usage
|
26
|
+
#
|
27
|
+
# stemplot(vec, [vec2], scale:, divider:, padchar:, trim: )
|
28
|
+
#
|
29
|
+
# ## Arguments
|
30
|
+
#
|
31
|
+
# - +vec+: Vector for which the stem leaf plot should be computed.
|
32
|
+
# - +vec2+: Optional secondary vector, will be used to create a back-to-back stem-leaf plot.
|
33
|
+
# - +scale+: Set scale of plot. Default = 10. Scale is changed via orders of magnitude. Common values are 0.1, 1, and 10. For String stems, the default value of 10 is a one character stem, 100 is a two character stem.
|
34
|
+
# - +divider+: Character for break between stem and leaf. Default = "|"
|
35
|
+
# - +padchar+: Character(s) to separate stems, leaves and dividers. Default = " "
|
36
|
+
# - +trim+: Trims the stem labels when there are no leaves. This can be useful if your data is sparse. Default = +false+
|
37
|
+
# - +string_padchar+: Character used to replace missing position for input strings shorter than the stem-size. Default = "_"
|
38
|
+
#
|
39
|
+
# ## Result
|
40
|
+
# A plot of object type is sent to <tt>$stdout</tt>
|
41
|
+
#
|
42
|
+
# @example Examples using Numbers
|
43
|
+
# # Generate some numbers
|
44
|
+
# fifty_floats = 50.times.map { rand(-1000..1000)/350.0 }
|
45
|
+
# eighty_ints = 80.times.map { rand(1..100) }
|
46
|
+
# another_eighty_ints = 80.times.map { rand(1..100) }
|
47
|
+
# three_hundred_ints = 300.times.map { rand(-100..100) }
|
48
|
+
#
|
49
|
+
# # Single sided stem-plot
|
50
|
+
# UnicodePlot.stemplot(eighty_ints)
|
51
|
+
#
|
52
|
+
# # Single sided stem-plot with positive and negative values
|
53
|
+
# UnicodePlot.stemplot(three_hundred_ints)
|
54
|
+
#
|
55
|
+
# # Single sided stem-plot using floating point values, scaled
|
56
|
+
# UnicodePlot.stemplot(fifty_floats, scale: 1)
|
57
|
+
#
|
58
|
+
# # Single sided stem-plot using floating point values, scaled with new divider
|
59
|
+
# UnicodePlot.stemplot(fifty_floats, scale: 1, divider: "😄")
|
60
|
+
#
|
61
|
+
# # Back to back stem-plot
|
62
|
+
# UnicodePlot.stemplot(eighty_ints, another_eighty_ints)
|
63
|
+
#
|
64
|
+
# @example Examples using Strings
|
65
|
+
# # Generate some strings
|
66
|
+
# words_1 = %w[apple junk ant age bee bar baz dog egg a]
|
67
|
+
# words_2 = %w[ape flan can cat juice elf gnome child fruit]
|
68
|
+
#
|
69
|
+
# # Single sided stem-plot
|
70
|
+
# UnicodePlot.stemplot(words_1)
|
71
|
+
#
|
72
|
+
# # Back to back stem-plot
|
73
|
+
# UnicodePlot.stemplot(words_1, words_2)
|
74
|
+
#
|
75
|
+
# # Scaled stem plot using scale=100 (two letters for the stem) and trimmed stems
|
76
|
+
# UnicodePlot.stemplot(words_1, scale: 100, trim: true)
|
77
|
+
#
|
78
|
+
# # Above, but changing the string_padchar
|
79
|
+
# UnicodePlot.stemplot(words_1, scale: 100, trim: true, string_padchar: '?')
|
80
|
+
|
81
|
+
class Stemplot
|
82
|
+
|
83
|
+
# Use {factory} method -- should not be directly called.
|
84
|
+
def initialize(*_args, **_kw)
|
85
|
+
@stemleafs = {}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Factory method to create a Stemplot, creates either a NumericStemplot
|
89
|
+
# or StringStemplot depending on input.
|
90
|
+
#
|
91
|
+
# @param vector [Array] An array of elements to stem-leaf plot
|
92
|
+
# @return [NumericStemplot] If all elements are Numeric
|
93
|
+
# @return [StringStemplot] If any elements are not Numeric
|
94
|
+
def self.factory(vector, **kw)
|
95
|
+
vec = Array(vector)
|
96
|
+
if vec.all? { |item| item.is_a?(Numeric) }
|
97
|
+
NumericStemplot.new(vec, **kw)
|
98
|
+
else
|
99
|
+
StringStemplot.new(vec, **kw)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Insert a stem and leaf
|
104
|
+
def insert(stem, leaf)
|
105
|
+
@stemleafs[stem] ||= []
|
106
|
+
@stemleafs[stem] << leaf
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns an unsorted list of stems
|
110
|
+
# @return [Array] Unsorted list of stems
|
111
|
+
def raw_stems
|
112
|
+
@stemleafs.keys
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns a list of leaves for a given stem
|
116
|
+
# @param stem [Object] The stem
|
117
|
+
# @return [Array] Unsorted list of leaves
|
118
|
+
def leaves(stem)
|
119
|
+
@stemleafs[stem] || []
|
120
|
+
end
|
121
|
+
|
122
|
+
# Determines largest length of any stem
|
123
|
+
# @return [Integer] Length value
|
124
|
+
def max_stem_length
|
125
|
+
@stemleafs.values.map(&:length).max
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns a sorted list of stems
|
129
|
+
# @param all [Boolean] Return all stems if true, otherwise only return stems if a leaf exists for a stem
|
130
|
+
# @return [Array] Sorted list of stems
|
131
|
+
def stems(all: true)
|
132
|
+
self.class.sorted_stem_list(raw_stems, all: all)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
class NumericStemplot < Stemplot
|
138
|
+
def initialize(vector, scale: 10, **kw)
|
139
|
+
super
|
140
|
+
Array(vector).each do |value|
|
141
|
+
fvalue = value.to_f.fdiv(scale/10.0)
|
142
|
+
stemnum = (fvalue/10).to_i
|
143
|
+
leafnum = (fvalue - (stemnum*10)).to_i
|
144
|
+
stemsign = value.negative? ? "-" : ''
|
145
|
+
stem = stemsign + stemnum.abs.to_s
|
146
|
+
leaf = leafnum.abs.to_s
|
147
|
+
self.insert(stem, leaf)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Print key to STDOUT
|
152
|
+
# @param scale [Integer] Scale, should be a power of 10
|
153
|
+
# @param divider [String] Divider character between stem and leaf
|
154
|
+
def print_key(scale, divider)
|
155
|
+
# First print the key
|
156
|
+
puts "Key: 1#{divider}0 = #{scale}"
|
157
|
+
# Description of where the decimal is
|
158
|
+
trunclog = Math.log10(scale).truncate
|
159
|
+
ndigits = trunclog.abs
|
160
|
+
right_or_left = (trunclog < 0) ? "left" : "right"
|
161
|
+
puts "The decimal is #{ndigits} digit(s) to the #{right_or_left} of #{divider}"
|
162
|
+
end
|
163
|
+
|
164
|
+
# Used when we have stems from a back-to-back stemplot and a combined list of stems is given
|
165
|
+
# @param stems [Array] Concatenated list of stems from two plots
|
166
|
+
# @param all [Boolean] Return all stems if true, otherwise only return stems if a leaf exists for a stem
|
167
|
+
# @return [Array] Sorted list of stems
|
168
|
+
def self.sorted_stem_list(stems, all: true)
|
169
|
+
negkeys, poskeys = stems.partition { |str| str[0] == '-'}
|
170
|
+
if all
|
171
|
+
negmin, negmax = negkeys.map(&:to_i).map(&:abs).minmax
|
172
|
+
posmin, posmax = poskeys.map(&:to_i).minmax
|
173
|
+
negrange = negmin ? (negmin..negmax).to_a.reverse.map { |s| "-"+s.to_s } : []
|
174
|
+
posrange = posmin ? (posmin..posmax).to_a.map(&:to_s) : []
|
175
|
+
return negrange + posrange
|
176
|
+
else
|
177
|
+
negkeys.sort! { |a,b| a.to_i <=> b.to_i }
|
178
|
+
poskeys.sort! { |a,b| a.to_i <=> b.to_i }
|
179
|
+
return negkeys + poskeys
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class StringStemplot < Stemplot
|
185
|
+
|
186
|
+
def initialize(vector, scale: 10, string_padchar: '_', **_kw)
|
187
|
+
super
|
188
|
+
stem_places = Math.log10(scale).floor
|
189
|
+
raise ArgumentError, "Cannot take fewer than 1 place from stem. Scale parameter should be greater than or equal to 10." if stem_places < 1
|
190
|
+
vector.each do |value|
|
191
|
+
# Strings may be shorter than the number of places we desire,
|
192
|
+
# so we will pad them with a string-pad-character.
|
193
|
+
padded_value = value.ljust(stem_places+1, string_padchar)
|
194
|
+
stem = padded_value[0...stem_places]
|
195
|
+
leaf = padded_value[stem_places]
|
196
|
+
self.insert(stem, leaf)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Function prototype to provide same interface as {NumericStemplot}.
|
201
|
+
# This function does not do anything.
|
202
|
+
# @return [false]
|
203
|
+
def print_key(_scale, _divider)
|
204
|
+
# intentionally empty
|
205
|
+
return false
|
206
|
+
end
|
207
|
+
|
208
|
+
# Used when we have stems from a back-to-back stemplot and a combined list of stems is given
|
209
|
+
# @param stems [Array] Concatenated list of stems from two plots
|
210
|
+
# @param all [Boolean] Return all stems if true, otherwise only return stems if a leaf exists for a stem
|
211
|
+
# @return [Array] Sorted list of stems
|
212
|
+
def self.sorted_stem_list(stems, all: true)
|
213
|
+
if all
|
214
|
+
rmin, rmax = stems.minmax
|
215
|
+
return (rmin .. rmax).to_a
|
216
|
+
else
|
217
|
+
stems.sort
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
# Print a Single-Vector stemplot to STDOUT.
|
224
|
+
#
|
225
|
+
# - Stem data is printed on the left.
|
226
|
+
# - Leaf data is printed on the right.
|
227
|
+
# - Key is printed at the bottom.
|
228
|
+
# @param plt [Stemplot] Stemplot object
|
229
|
+
# @param scale [Integer] Scale, should be a power of 10
|
230
|
+
# @param divider [String] Divider character between stem and leaf
|
231
|
+
# @param padchar [String] Padding character
|
232
|
+
# @param trim [Boolean] Trim missing stems from the plot
|
233
|
+
def stemplot1!(plt,
|
234
|
+
scale: 10,
|
235
|
+
divider: "|",
|
236
|
+
padchar: " ",
|
237
|
+
trim: false,
|
238
|
+
**_kw
|
239
|
+
)
|
240
|
+
|
241
|
+
stem_labels = plt.stems(all: !trim)
|
242
|
+
label_len = stem_labels.map(&:length).max
|
243
|
+
column_len = label_len + 1
|
244
|
+
|
245
|
+
stem_labels.each do |stem|
|
246
|
+
leaves = plt.leaves(stem).sort
|
247
|
+
stemlbl = stem.rjust(label_len, padchar).ljust(column_len, padchar)
|
248
|
+
puts stemlbl + divider + padchar + leaves.join
|
249
|
+
end
|
250
|
+
plt.print_key(scale, divider)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Print a Back-to-Back Stemplot to STDOUT
|
254
|
+
#
|
255
|
+
# - +plt1+ Leaf data is printed on the left.
|
256
|
+
# - Common stem data is printed in the center.
|
257
|
+
# - +plt2+ Leaf data is printed on the right.
|
258
|
+
# - Key is printed at the bottom.
|
259
|
+
# @param plt1 [Stemplot] Stemplot object for the left side
|
260
|
+
# @param plt2 [Stemplot] Stemplot object for the right side
|
261
|
+
# @param scale [Integer] Scale, should be a power of 10
|
262
|
+
# @param divider [String] Divider character between stem and leaf
|
263
|
+
# @param padchar [String] Padding character
|
264
|
+
# @param trim [Boolean] Trim missing stems from the plot
|
265
|
+
def stemplot2!(plt1, plt2,
|
266
|
+
scale: 10,
|
267
|
+
divider: "|",
|
268
|
+
padchar: " ",
|
269
|
+
trim: false,
|
270
|
+
**_kw
|
271
|
+
)
|
272
|
+
stem_labels = plt1.class.sorted_stem_list( (plt1.raw_stems + plt2.raw_stems).uniq, all: !trim )
|
273
|
+
label_len = stem_labels.map(&:length).max
|
274
|
+
column_len = label_len + 1
|
275
|
+
|
276
|
+
leftleaf_len = plt1.max_stem_length
|
277
|
+
|
278
|
+
stem_labels.each do |stem|
|
279
|
+
left_leaves = plt1.leaves(stem).sort.join('')
|
280
|
+
right_leaves = plt2.leaves(stem).sort.join('')
|
281
|
+
left_leaves_just = left_leaves.reverse.rjust(leftleaf_len, padchar)
|
282
|
+
stem = stem.rjust(column_len, padchar).ljust(column_len+1, padchar)
|
283
|
+
puts left_leaves_just + padchar + divider + stem + divider + padchar + right_leaves
|
284
|
+
end
|
285
|
+
|
286
|
+
plt1.print_key(scale, divider)
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
# Generates one or more {Stemplot} objects from the input data
|
291
|
+
# and prints a Single or Double stemplot using {stemplot1!} or {stemplot2!}
|
292
|
+
# @see Stemplot
|
293
|
+
# @example Single sided stemplot
|
294
|
+
# >> UnicodePlot.stemplot(eighty_ints)
|
295
|
+
# 0 | 257
|
296
|
+
# 1 | 00335679
|
297
|
+
# 2 | 034455899
|
298
|
+
# 3 | 145588
|
299
|
+
# 4 | 0022223
|
300
|
+
# 5 | 0223399
|
301
|
+
# 6 | 012345568889
|
302
|
+
# 7 | 01133334466777888
|
303
|
+
# 8 | 013689
|
304
|
+
# 9 | 22667
|
305
|
+
# Key: 1|0 = 10
|
306
|
+
# The decimal is 1 digit(s) to the right of |
|
307
|
+
#
|
308
|
+
# @example Back-to-back stemplot
|
309
|
+
# >> UnicodePlot.stemplot(eighty_ints, another_eighty_ints)
|
310
|
+
# 752 | 0 | 1244457899
|
311
|
+
# 97653300 | 1 | 4799
|
312
|
+
# 998554430 | 2 | 015668
|
313
|
+
# 885541 | 3 | 0144557888899
|
314
|
+
# 3222200 | 4 | 00268
|
315
|
+
# 9933220 | 5 | 0234778
|
316
|
+
# 988865543210 | 6 | 122222357889
|
317
|
+
# 88877766443333110 | 7 | 134556689
|
318
|
+
# 986310 | 8 | 24589
|
319
|
+
# 76622 | 9 | 022234468
|
320
|
+
# Key: 1|0 = 10
|
321
|
+
# The decimal is 1 digit(s) to the right of |
|
322
|
+
#
|
323
|
+
def stemplot(*args, scale: 10, **kw)
|
324
|
+
case args.length
|
325
|
+
when 1
|
326
|
+
# Stemplot object
|
327
|
+
plt = Stemplot.factory(args[0], scale: scale, **kw)
|
328
|
+
# Dispatch to plot routine
|
329
|
+
stemplot1!(plt, scale: scale, **kw)
|
330
|
+
when 2
|
331
|
+
# Stemplot object
|
332
|
+
plt1 = Stemplot.factory(args[0], scale: scale)
|
333
|
+
plt2 = Stemplot.factory(args[1], scale: scale)
|
334
|
+
raise ArgumentError, "Plot types must be the same for back-to-back stemplot " +
|
335
|
+
"#{plt1.class} != #{plt2.class}" unless plt1.class == plt2.class
|
336
|
+
# Dispatch to plot routine
|
337
|
+
stemplot2!(plt1, plt2, scale: scale, **kw)
|
338
|
+
else
|
339
|
+
raise ArgumentError, "Expecting one or two arguments"
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
module_function :stemplot, :stemplot1!, :stemplot2!
|
344
|
+
end
|