rubyplot 0.0.1 → 0.1.pre.a1
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 +5 -5
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +133 -0
- data/.travis.yml +18 -0
- data/CHANGELOG.md +9 -0
- data/CONTRIBUTING.md +48 -0
- data/Gemfile +6 -0
- data/README.md +47 -0
- data/Rakefile +32 -0
- data/ext/grruby/extconf.rb +6 -0
- data/ext/grruby/grruby.c +1163 -0
- data/ext/grruby/grruby.h +135 -0
- data/lib/rubyplot.rb +30 -1
- data/lib/rubyplot/artist.rb +13 -0
- data/lib/rubyplot/artist/axes.rb +328 -0
- data/lib/rubyplot/artist/axis.rb +3 -0
- data/lib/rubyplot/artist/axis/base.rb +34 -0
- data/lib/rubyplot/artist/axis/x_axis.rb +35 -0
- data/lib/rubyplot/artist/axis/y_axis.rb +40 -0
- data/lib/rubyplot/artist/base.rb +14 -0
- data/lib/rubyplot/artist/circle.rb +28 -0
- data/lib/rubyplot/artist/figure.rb +90 -0
- data/lib/rubyplot/artist/legend.rb +59 -0
- data/lib/rubyplot/artist/legend_box.rb +89 -0
- data/lib/rubyplot/artist/line2d.rb +24 -0
- data/lib/rubyplot/artist/plot.rb +9 -0
- data/lib/rubyplot/artist/plot/area.rb +38 -0
- data/lib/rubyplot/artist/plot/bar.rb +69 -0
- data/lib/rubyplot/artist/plot/bar_type.rb +31 -0
- data/lib/rubyplot/artist/plot/base.rb +67 -0
- data/lib/rubyplot/artist/plot/bubble.rb +41 -0
- data/lib/rubyplot/artist/plot/line.rb +61 -0
- data/lib/rubyplot/artist/plot/multi_bars.rb +75 -0
- data/lib/rubyplot/artist/plot/multi_stacked_bar.rb +78 -0
- data/lib/rubyplot/artist/plot/scatter.rb +29 -0
- data/lib/rubyplot/artist/plot/stacked_bar.rb +69 -0
- data/lib/rubyplot/artist/polygon.rb +21 -0
- data/lib/rubyplot/artist/rectangle.rb +39 -0
- data/lib/rubyplot/artist/text.rb +49 -0
- data/lib/rubyplot/artist/tick.rb +3 -0
- data/lib/rubyplot/artist/tick/base.rb +35 -0
- data/lib/rubyplot/artist/tick/x_tick.rb +25 -0
- data/lib/rubyplot/artist/tick/y_tick.rb +24 -0
- data/lib/rubyplot/backend.rb +2 -0
- data/lib/rubyplot/backend/gr_wrapper.rb +7 -0
- data/lib/rubyplot/backend/magick_wrapper.rb +141 -0
- data/lib/rubyplot/color.rb +992 -0
- data/lib/rubyplot/figure.rb +2 -0
- data/lib/rubyplot/spi.rb +8 -0
- data/lib/rubyplot/subplot.rb +4 -0
- data/lib/rubyplot/themes.rb +47 -0
- data/lib/rubyplot/utils.rb +14 -0
- data/lib/rubyplot/version.rb +1 -1
- data/rubyplot.gemspec +10 -0
- data/spec/axes_spec.rb +477 -0
- data/spec/figure_spec.rb +12 -0
- data/spec/spec_helper.rb +62 -0
- data/spec/spi/multi_plot_graph_spec.rb +33 -0
- data/spec/spi/single_plot_graph_spec.rb +227 -0
- data/spec/spi/subplots_spec.rb +55 -0
- metadata +166 -8
@@ -0,0 +1,69 @@
|
|
1
|
+
module Rubyplot
|
2
|
+
module Artist
|
3
|
+
module Plot
|
4
|
+
class Bar < Artist::Plot::Base
|
5
|
+
# Space between the columns.
|
6
|
+
attr_accessor :bar_spacing
|
7
|
+
# Width of each bar in pixels.
|
8
|
+
attr_accessor :bar_width
|
9
|
+
# Number between 0 and 1.0 denoting spacing between the bars.
|
10
|
+
# 0.0 means no spacing at all 1.0 means that each bars' width
|
11
|
+
# is nearly 0 (so each bar is a simple line with no X dimension).
|
12
|
+
# Denotes the total left + right side space.
|
13
|
+
attr_reader :spacing_ratio
|
14
|
+
# X co-ordinates of the lower left corner of the bar.
|
15
|
+
attr_accessor :abs_x_left
|
16
|
+
# Y co-ordinates of the lower left corner of the bar.
|
17
|
+
attr_accessor :abs_y_left
|
18
|
+
|
19
|
+
def initialize(*)
|
20
|
+
super
|
21
|
+
@spacing_ratio = 0.1
|
22
|
+
@abs_x_left = []
|
23
|
+
@abs_y_left = []
|
24
|
+
@rectangles = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# Set the spacing factor for this bar plot.
|
28
|
+
def spacing_factor=(s_f)
|
29
|
+
raise ValueError, '@spacing_factor must be between 0.00 and 1.00' unless
|
30
|
+
(s_f >= 0) && (s_f <= 1)
|
31
|
+
|
32
|
+
@spacing_factor = s_f
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set Bar plot data.
|
36
|
+
def data y_values
|
37
|
+
super(Array.new(y_values.size) { |i| i }, y_values)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Number of bars in this Bar plot
|
41
|
+
def num_bars
|
42
|
+
@data[:y_values].size
|
43
|
+
end
|
44
|
+
|
45
|
+
def draw
|
46
|
+
setup_bar_rectangles
|
47
|
+
@rectangles.each(&:draw)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def setup_bar_rectangles
|
53
|
+
@normalized_data[:y_values].each_with_index do |iy, i|
|
54
|
+
height = iy * @axes.y_axis.length
|
55
|
+
@rectangles << Rubyplot::Artist::Rectangle.new(
|
56
|
+
self,
|
57
|
+
abs_x: @abs_x_left[i],
|
58
|
+
abs_y: @abs_y_left[i] - height,
|
59
|
+
width: @bar_width,
|
60
|
+
height: height,
|
61
|
+
border_color: @data[:color],
|
62
|
+
fill_color: @data[:color]
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end # class Bar
|
67
|
+
end # module Plot
|
68
|
+
end # module Artist
|
69
|
+
end # module Rubyplot
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rubyplot
|
2
|
+
module Artist
|
3
|
+
module Plot
|
4
|
+
class BarType < Artist::Plot::Base
|
5
|
+
def initialize(*)
|
6
|
+
super
|
7
|
+
@spacing_ratio = 0.1
|
8
|
+
@abs_x_left = []
|
9
|
+
@abs_y_left = []
|
10
|
+
@rectangles = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def data y_values
|
14
|
+
super(Array(0...(y_values.size)), y_values)
|
15
|
+
end
|
16
|
+
|
17
|
+
def num_bars
|
18
|
+
@data[:y_values].size
|
19
|
+
end
|
20
|
+
|
21
|
+
def draw
|
22
|
+
setup_bar_rectangles
|
23
|
+
@rectangles.each(&:draw)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
end # class BarType
|
29
|
+
end # module Plot
|
30
|
+
end # module Artist
|
31
|
+
end # module Rubyplot
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Rubyplot
|
2
|
+
module Artist
|
3
|
+
module Plot
|
4
|
+
class Base < Artist::Base
|
5
|
+
attr_reader :axes, :data, :x_max, :x_min, :y_min, :y_max
|
6
|
+
attr_writer :stroke_width, :stroke_opacity
|
7
|
+
|
8
|
+
def initialize axes
|
9
|
+
super(axes.abs_x, axes.abs_y)
|
10
|
+
@axes = axes
|
11
|
+
@data = {
|
12
|
+
label: '',
|
13
|
+
color: :default
|
14
|
+
}
|
15
|
+
@normalized_data = {
|
16
|
+
y_values: nil,
|
17
|
+
x_values: nil
|
18
|
+
}
|
19
|
+
@stroke_width = 4.0
|
20
|
+
@stroke_opacity = 0.0
|
21
|
+
end
|
22
|
+
|
23
|
+
def label
|
24
|
+
@data[:label]
|
25
|
+
end
|
26
|
+
|
27
|
+
def color
|
28
|
+
@data[:color]
|
29
|
+
end
|
30
|
+
|
31
|
+
def label=(label)
|
32
|
+
@data[:label] = label
|
33
|
+
end
|
34
|
+
|
35
|
+
def color= color
|
36
|
+
@data[:color] = color
|
37
|
+
end
|
38
|
+
|
39
|
+
def data(x_values, y_values)
|
40
|
+
@data[:x_values] = x_values
|
41
|
+
@data[:y_values] = y_values
|
42
|
+
@y_min = @data[:y_values].min
|
43
|
+
@y_max = @data[:y_values].max
|
44
|
+
@x_min = @data[:x_values].min
|
45
|
+
@x_max = @data[:x_values].max
|
46
|
+
end
|
47
|
+
|
48
|
+
# Normalize original data to values between 0-1. Used for obtaining relative
|
49
|
+
# values of the data.
|
50
|
+
def normalize
|
51
|
+
x_spread = @axes.x_range[1] - @axes.x_range[0]
|
52
|
+
y_spread = @axes.y_range[1] - @axes.y_range[0]
|
53
|
+
if @data[:x_values]
|
54
|
+
@normalized_data[:x_values] = @data[:x_values].map do |x|
|
55
|
+
(x.to_f - @axes.x_range[0]) / x_spread
|
56
|
+
end
|
57
|
+
end
|
58
|
+
if @data[:y_values]
|
59
|
+
@normalized_data[:y_values] = @data[:y_values].map do |y|
|
60
|
+
(y.to_f - @axes.y_range[0]) / y_spread
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end # class Base
|
65
|
+
end # module Plot
|
66
|
+
end # module Artist
|
67
|
+
end # module Rubyplot
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Rubyplot
|
2
|
+
module Artist
|
3
|
+
module Plot
|
4
|
+
class Bubble < Artist::Plot::Base
|
5
|
+
# Width in pixels of the border of each bubble.
|
6
|
+
attr_reader :stroke_width
|
7
|
+
attr_reader :z_max, :z_min
|
8
|
+
def initialize(*)
|
9
|
+
super
|
10
|
+
@bubbles = []
|
11
|
+
@stroke_width = 1.0
|
12
|
+
end
|
13
|
+
|
14
|
+
def data x_values, y_values, z_values
|
15
|
+
super(x_values, y_values)
|
16
|
+
@data[:z_values] = z_values
|
17
|
+
@z_max = @data[:z_values].max
|
18
|
+
@z_min = @data[:z_values].min
|
19
|
+
end
|
20
|
+
|
21
|
+
def draw
|
22
|
+
@normalized_data[:y_values].each_with_index do |iy, idx_y|
|
23
|
+
ix = @normalized_data[:x_values][idx_y]
|
24
|
+
iz = @data[:z_values][idx_y]
|
25
|
+
abs_x = ix * @axes.x_axis.length + @axes.abs_x + @axes.y_axis_margin
|
26
|
+
abs_y = (@axes.y_axis.length - iy * @axes.y_axis.length) + @axes.abs_y
|
27
|
+
@bubbles << Rubyplot::Artist::Circle.new(
|
28
|
+
self,
|
29
|
+
abs_x: abs_x,
|
30
|
+
abs_y: abs_y,
|
31
|
+
radius: iz,
|
32
|
+
color: @data[:color],
|
33
|
+
stroke_width: @stroke_width
|
34
|
+
)
|
35
|
+
end
|
36
|
+
@bubbles.each(&:draw)
|
37
|
+
end
|
38
|
+
end # class Bubble
|
39
|
+
end # module Plot
|
40
|
+
end # module Artist
|
41
|
+
end # module Rubyplot
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Rubyplot
|
2
|
+
module Artist
|
3
|
+
module Plot
|
4
|
+
class Line < Artist::Plot::Base
|
5
|
+
# Set true if you want to see only the vertices of the line plot.
|
6
|
+
attr_writer :hide_lines
|
7
|
+
|
8
|
+
def initialize(*)
|
9
|
+
super
|
10
|
+
@hide_lines = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def data(x_values, y_values=[])
|
14
|
+
y_values = Array.new(x_values.size) { |i| i } if y_values.empty?
|
15
|
+
super x_values, y_values
|
16
|
+
end
|
17
|
+
|
18
|
+
def draw
|
19
|
+
if @normalized_data[:x_values].size == 1
|
20
|
+
draw_single_point
|
21
|
+
else
|
22
|
+
draw_lines
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# FIXME: make this edge case happen.
|
29
|
+
def draw_single_point
|
30
|
+
# Rubyplot::Artist::Circle.new()
|
31
|
+
end
|
32
|
+
|
33
|
+
def draw_lines
|
34
|
+
prev_x = prev_y = nil
|
35
|
+
@normalized_data[:x_values].each_with_index do |ix, idx_ix|
|
36
|
+
iy = @normalized_data[:y_values][idx_ix]
|
37
|
+
next if ix.nil? || iy.nil?
|
38
|
+
|
39
|
+
new_x = ix * (@axes.x_axis.abs_x2 - @axes.x_axis.abs_x1).abs + @axes.abs_x +
|
40
|
+
@axes.y_axis_margin
|
41
|
+
new_y = (@axes.y_axis.length - iy * @axes.y_axis.length) + @axes.abs_y
|
42
|
+
|
43
|
+
unless prev_x.nil? && prev_y.nil?
|
44
|
+
Rubyplot::Artist::Line2D.new(
|
45
|
+
self,
|
46
|
+
abs_x1: prev_x,
|
47
|
+
abs_y1: prev_y,
|
48
|
+
abs_x2: new_x,
|
49
|
+
abs_y2: new_y,
|
50
|
+
stroke_opacity: @stroke_opacity,
|
51
|
+
stroke_width: @stroke_width
|
52
|
+
).draw
|
53
|
+
end
|
54
|
+
prev_x = new_x
|
55
|
+
prev_y = new_y
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end # class Line
|
59
|
+
end # module Plot
|
60
|
+
end # module Artist
|
61
|
+
end # module Rubyplot
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Rubyplot
|
2
|
+
module Artist
|
3
|
+
module Plot
|
4
|
+
# Class for holding multiple Bar plot objects.
|
5
|
+
# Terminoligies used:
|
6
|
+
#
|
7
|
+
# * A 'bar' is a single bar of a single bar plot.
|
8
|
+
# * A 'slot' is a box within which multiple bars can be plotted.
|
9
|
+
# * 'padding' is the total whitespace on the left and right of a slot.
|
10
|
+
class MultiBars < Artist::Plot::Base
|
11
|
+
# The max. width that each bar can occupy.
|
12
|
+
attr_reader :max_bar_width
|
13
|
+
|
14
|
+
def initialize(*args, bar_plots:)
|
15
|
+
super(args[0])
|
16
|
+
@bar_plots = bar_plots
|
17
|
+
@x_min = @bar_plots.map(&:x_min).min
|
18
|
+
@y_min = @bar_plots.map(&:y_min).min
|
19
|
+
@x_max = @bar_plots.map(&:x_max).max
|
20
|
+
@y_max = @bar_plots.map(&:y_max).max
|
21
|
+
configure_plot_geometry_data!
|
22
|
+
configure_x_ticks!
|
23
|
+
end
|
24
|
+
|
25
|
+
def normalize
|
26
|
+
@bar_plots.each(&:normalize)
|
27
|
+
end
|
28
|
+
|
29
|
+
def draw
|
30
|
+
@bar_plots.each(&:draw)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def configure_plot_geometry_data!
|
36
|
+
@num_max_slots = @bar_plots.map(&:num_bars).max
|
37
|
+
@max_slot_width = (@axes.x_axis.abs_x2 - @axes.x_axis.abs_x1).abs / @num_max_slots
|
38
|
+
# FIXME: figure out a way to specify inter-box space somehow.
|
39
|
+
@spacing_ratio = @bar_plots[0].spacing_ratio
|
40
|
+
@padding = @spacing_ratio * @max_slot_width
|
41
|
+
@max_bars_width = @max_slot_width - @padding
|
42
|
+
@bars_per_slot = @bar_plots.size
|
43
|
+
@bar_plots.each_with_index do |bar, index|
|
44
|
+
set_bar_dims bar, index
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def configure_x_ticks!
|
49
|
+
@axes.num_x_ticks = @num_max_slots
|
50
|
+
labels = @axes.x_ticks || Array.new(@num_max_slots, &:to_s)
|
51
|
+
labels = labels[0...@axes.num_x_ticks] if labels.size != @axes.num_x_ticks
|
52
|
+
@axes.x_ticks = labels.map.with_index do |label, i|
|
53
|
+
Rubyplot::Artist::XTick.new(
|
54
|
+
@axes,
|
55
|
+
abs_x: @axes.abs_x + @axes.y_axis_margin + i * @max_slot_width + @max_slot_width / 2,
|
56
|
+
abs_y: @axes.origin[1],
|
57
|
+
label: label,
|
58
|
+
length: 6,
|
59
|
+
label_distance: 10
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_bar_dims bar_plot, index
|
65
|
+
bar_plot.bar_width = @max_bars_width / @bars_per_slot
|
66
|
+
@num_max_slots.times do |i|
|
67
|
+
bar_plot.abs_x_left[i] = @axes.abs_x + @axes.y_axis_margin +
|
68
|
+
i * @max_slot_width + @padding / 2 + index * bar_plot.bar_width
|
69
|
+
bar_plot.abs_y_left[i] = @axes.origin[1] - @axes.x_axis.stroke_width
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end # class MultiBars
|
73
|
+
end # module Plot
|
74
|
+
end # module Artist
|
75
|
+
end # module Rubyplot
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Rubyplot
|
2
|
+
module Artist
|
3
|
+
module Plot
|
4
|
+
class MultiStackedBar < Artist::Plot::Base
|
5
|
+
def initialize(*args, stacked_bars:)
|
6
|
+
super(args[0])
|
7
|
+
@stacked_bars = stacked_bars
|
8
|
+
@x_min = @stacked_bars.map(&:x_min).min
|
9
|
+
@y_min = @stacked_bars.map(&:y_min).min
|
10
|
+
@x_max = @stacked_bars.map(&:x_max).max
|
11
|
+
@y_max = @stacked_bars.map(&:y_max).max
|
12
|
+
reset_axes_ranges
|
13
|
+
renormalize_data
|
14
|
+
configure_plot_geometry_data
|
15
|
+
configure_x_ticks
|
16
|
+
end
|
17
|
+
|
18
|
+
def draw
|
19
|
+
@stacked_bars.each(&:draw)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def reset_axes_ranges
|
25
|
+
@axes.y_range[0] = 0
|
26
|
+
@axes.x_range[0] = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# Normalize data in the stacked bar plot w.r.t each other and new X & Y ranges.
|
30
|
+
def renormalize_data
|
31
|
+
@stacked_bars.each do |p|
|
32
|
+
p.normalize
|
33
|
+
p.renormalize @stacked_bars.size
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def configure_plot_geometry_data
|
38
|
+
@num_max_slots = @stacked_bars.map(&:num_bars).max
|
39
|
+
@max_slot_width = @axes.x_axis.length / @num_max_slots
|
40
|
+
@spacing_ratio = @stacked_bars[0].spacing_ratio
|
41
|
+
@padding = @spacing_ratio * @max_slot_width
|
42
|
+
@max_bars_width = @max_slot_width - @padding
|
43
|
+
@num_max_stacks = @stacked_bars.size
|
44
|
+
@stacked_bars.each_with_index do |bar, index|
|
45
|
+
set_bar_dims bar, index
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def configure_x_ticks
|
50
|
+
@axes.num_x_ticks = @num_max_slots
|
51
|
+
labels = @axes.x_ticks || Array.new(@num_max_slots, &:to_s)
|
52
|
+
labels = labels[0...@axes.num_x_ticks] if labels.size != @axes.num_x_ticks
|
53
|
+
@axes.x_ticks = labels.map.with_index do |label, i|
|
54
|
+
Rubyplot::Artist::XTick.new(
|
55
|
+
@axes,
|
56
|
+
abs_x: @axes.abs_x + @axes.y_axis_margin + i * @max_slot_width + @max_slot_width / 2,
|
57
|
+
abs_y: @axes.origin[1],
|
58
|
+
label: label,
|
59
|
+
length: 6,
|
60
|
+
label_distance: 10
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_bar_dims bar, plot_index
|
66
|
+
bar.bar_width = @max_bars_width
|
67
|
+
plots_below = @stacked_bars[0...plot_index]
|
68
|
+
bar.num_bars.times do |i|
|
69
|
+
pedestal_height = plots_below.map { |p| p.member_height(i) }.inject(:+) || 0
|
70
|
+
bar.abs_x_left[i] = @axes.abs_x + @axes.y_axis_margin +
|
71
|
+
i * @max_slot_width + @padding / 2
|
72
|
+
bar.abs_y_left[i] = (@axes.abs_y + @axes.y_axis.length) - pedestal_height
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end # class StackedBar
|
76
|
+
end # module Plot
|
77
|
+
end # module Artist
|
78
|
+
end # module Rubyplot
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rubyplot
|
2
|
+
module Artist
|
3
|
+
module Plot
|
4
|
+
class Scatter < Artist::Plot::Base
|
5
|
+
attr_writer :circle_radius
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
@circle_radius = 4.0
|
10
|
+
end
|
11
|
+
|
12
|
+
def draw
|
13
|
+
@normalized_data[:y_values].each_with_index do |iy, idx_y|
|
14
|
+
ix = @normalized_data[:x_values][idx_y]
|
15
|
+
next if iy.nil? || ix.nil?
|
16
|
+
|
17
|
+
abs_x = ix * @axes.x_axis.length + @axes.abs_x + @axes.y_axis_margin
|
18
|
+
abs_y = (@axes.y_axis.length - iy * @axes.y_axis.length) + @axes.abs_y
|
19
|
+
Rubyplot::Artist::Circle.new(
|
20
|
+
self, abs_x: abs_x, abs_y: abs_y, radius: @circle_radius,
|
21
|
+
stroke_opacity: @stroke_opacity,
|
22
|
+
stroke_width: @stroke_width
|
23
|
+
).draw
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end # class Scatter
|
27
|
+
end # module Plot
|
28
|
+
end # module Artist
|
29
|
+
end # module Rubyplot
|