rust 0.11 → 0.12
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 +4 -4
- data/lib/rust/core/types/language.rb +1 -1
- data/lib/rust/external/ggplot2/core.rb +171 -0
- data/lib/rust/external/ggplot2/geoms.rb +83 -0
- data/lib/rust/external/ggplot2/helper.rb +122 -0
- data/lib/rust/external/ggplot2/plot_builder.rb +188 -0
- data/lib/rust/external/ggplot2/themes.rb +435 -0
- data/lib/rust/external/ggplot2.rb +5 -0
- data/lib/rust.rb +19 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8eb6e3759ef38070603a941ef348ac46e4b6c08b4638c493edb2169acf16c793
|
4
|
+
data.tar.gz: f855ca774695688ee64d7513ac94c8b98d3bae154bfae56b803ed1da567cb282
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c281de698d8b4750832d77971dac857dbee2c31252b7950abf91777d37763bbb79577098ab48efeee6f8a5ef2a28949ef72816d33703eaad14887d588b4aac32
|
7
|
+
data.tar.gz: d44c0532c19ff8eb2f505d4da62e82b4b4f32dada4b39a05cddf57679a3a725dd38ceecf63aaf2d551a8ae691841196bad7cc0694d3613b928a84a9b6291ef6a
|
@@ -137,7 +137,7 @@ module Rust
|
|
137
137
|
# Sets the +arguments+ (Arguments type) of the function.
|
138
138
|
|
139
139
|
def arguments=(arguments)
|
140
|
-
raise TypeError, "Expected Arguments" unless
|
140
|
+
raise TypeError, "Expected Arguments" unless arguments.is_a?(Arguments)
|
141
141
|
|
142
142
|
@arguments = arguments
|
143
143
|
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require_relative '../../../rust'
|
2
|
+
|
3
|
+
Rust.prerequisite("ggplot2")
|
4
|
+
|
5
|
+
module Rust::Plots::GGPlot
|
6
|
+
def self.default_theme
|
7
|
+
@@theme
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.default_theme=(theme)
|
11
|
+
@@theme = theme.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
class Layer
|
15
|
+
def initialize(function_name, **options)
|
16
|
+
@function_name = function_name
|
17
|
+
@arguments = Rust::Arguments.new
|
18
|
+
@options = Rust::Options.from_hash(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def option(key, value)
|
22
|
+
@options[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_R
|
26
|
+
if !block_given?
|
27
|
+
options, arguments = @options, @arguments
|
28
|
+
else
|
29
|
+
options, arguments = yield(@options, @arguments)
|
30
|
+
end
|
31
|
+
|
32
|
+
options = Rust::Options.from_hash(options) unless options.is_a?(Rust::Options)
|
33
|
+
|
34
|
+
function = Rust::Function.new(@function_name)
|
35
|
+
function.arguments = arguments if arguments
|
36
|
+
function.options = options if options
|
37
|
+
return function.to_R
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Aes
|
42
|
+
def initialize(**options)
|
43
|
+
options = options.map { |k, v| [k, v.is_a?(Symbol) ? Rust::Variable.new(v) : v] }.to_h
|
44
|
+
@options = Rust::Options.from_hash(options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_R
|
48
|
+
function = Rust::Function.new("aes")
|
49
|
+
function.options = @options if @options
|
50
|
+
return function.to_R
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Plot
|
55
|
+
attr_accessor :theme
|
56
|
+
attr_accessor :aes
|
57
|
+
|
58
|
+
def initialize(data, aes = nil)
|
59
|
+
@layers = []
|
60
|
+
|
61
|
+
@data = data
|
62
|
+
@aes = aes
|
63
|
+
@theme = Rust::Plots::GGPlot.default_theme
|
64
|
+
end
|
65
|
+
|
66
|
+
def layer(layer)
|
67
|
+
@layers << layer
|
68
|
+
end
|
69
|
+
|
70
|
+
def show()
|
71
|
+
Rust.exclusive do
|
72
|
+
dataset_name = nil
|
73
|
+
if @data
|
74
|
+
dataset_name = "ggplotter.data"
|
75
|
+
@data.load_in_r_as(dataset_name)
|
76
|
+
end
|
77
|
+
r = self.to_R(dataset_name)
|
78
|
+
Rust._eval(r)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def save(filename, **options)
|
83
|
+
Rust.exclusive do
|
84
|
+
dataset_name = nil
|
85
|
+
if @data
|
86
|
+
dataset_name = "ggplotter.data"
|
87
|
+
@data.load_in_r_as(dataset_name)
|
88
|
+
end
|
89
|
+
r = self.to_R(dataset_name)
|
90
|
+
Rust._eval("ggplot.latest <- #{r}")
|
91
|
+
save = Rust::Function.new("ggsave")
|
92
|
+
save.arguments = Rust::Arguments.new([Rust::Variable.new("ggplot.latest")])
|
93
|
+
save.options = Rust::Options.from_hash({file: filename}.merge(options))
|
94
|
+
save.call
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_R(data_set_name="ggplotter.data")
|
99
|
+
function = Rust::Function.new("ggplot")
|
100
|
+
function.arguments = Rust::Arguments.new
|
101
|
+
function.arguments << (data_set_name ? Rust::Variable.new(data_set_name) : nil)
|
102
|
+
function.arguments << @aes if @aes
|
103
|
+
|
104
|
+
result = function.to_R
|
105
|
+
result += " + " + @theme.to_R
|
106
|
+
@layers.each do |layer|
|
107
|
+
result += " + " + layer.to_R
|
108
|
+
end
|
109
|
+
|
110
|
+
return result
|
111
|
+
end
|
112
|
+
|
113
|
+
def <<(others)
|
114
|
+
if others.is_a?(Array) && others.all? { |o| o.is_a?(Layer) }
|
115
|
+
@layers += others
|
116
|
+
elsif others.is_a?(Layer)
|
117
|
+
@layers << others
|
118
|
+
else
|
119
|
+
raise ArgumentError, "Expected Layer or Array of Layers"
|
120
|
+
end
|
121
|
+
|
122
|
+
return self
|
123
|
+
end
|
124
|
+
|
125
|
+
def +(others)
|
126
|
+
copy = self.deep_clone
|
127
|
+
copy << others
|
128
|
+
return copy
|
129
|
+
end
|
130
|
+
|
131
|
+
def inspect(show = true)
|
132
|
+
self.show if show
|
133
|
+
return super()
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Labels < Layer
|
138
|
+
def initialize(**options)
|
139
|
+
super("labs", **options)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class FlipCoordinates < Layer
|
144
|
+
def initialize(**options)
|
145
|
+
super("coord_flip", **options)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
module Rust::RBindings
|
151
|
+
def ggplot(*arguments)
|
152
|
+
Rust::Plots::GGPlot::Plot.new(*arguments)
|
153
|
+
end
|
154
|
+
|
155
|
+
def aes(**options)
|
156
|
+
Rust::Plots::GGPlot::Aes.new(**options)
|
157
|
+
end
|
158
|
+
|
159
|
+
def labs(**options)
|
160
|
+
Rust::Plots::GGPlot::Labels.new(**options)
|
161
|
+
end
|
162
|
+
alias :labels :labs
|
163
|
+
|
164
|
+
def coord_flip(**options)
|
165
|
+
Rust::Plots::GGPlot::FlipCoordinates.new(**options)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def bind_ggplot!
|
170
|
+
include Rust::Plots::GGPlot
|
171
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require_relative 'core'
|
2
|
+
|
3
|
+
module Rust::Plots::GGPlot
|
4
|
+
class Geom < Layer
|
5
|
+
def initialize(type, arguments = [], **options)
|
6
|
+
super("geom_#{type}", **options)
|
7
|
+
@type = type
|
8
|
+
@arguments = Rust::Arguments.new(arguments)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class GeomPoint < Geom
|
13
|
+
def initialize(arguments = [], **options)
|
14
|
+
super("point", arguments, **options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class GeomLine < Geom
|
19
|
+
def initialize(arguments = [], **options)
|
20
|
+
super("line", arguments, **options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class GeomCol < Geom
|
25
|
+
def initialize(arguments = [], **options)
|
26
|
+
super("col", arguments, **options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class GeomBoxplot < Geom
|
31
|
+
def initialize(arguments = [], **options)
|
32
|
+
super("boxplot", arguments, **options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class GeomBar < Geom
|
37
|
+
def initialize(arguments = [], **options)
|
38
|
+
super("bar", arguments, **options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class GeomHistogram < Geom
|
43
|
+
def initialize(arguments = [], **options)
|
44
|
+
super("histogram", arguments, **options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class GeomDensity < Geom
|
49
|
+
def initialize(arguments = [], **options)
|
50
|
+
super("density", arguments, **options)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module Rust::RBindings
|
56
|
+
def geom_point(*arguments, **options)
|
57
|
+
return Rust::Plots::GGPlot::GeomPoint.new(*arguments, **options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def geom_line(*arguments, **options)
|
61
|
+
return Rust::Plots::GGPlot::GeomLine.new(*arguments, **options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def geom_col(*arguments, **options)
|
65
|
+
return Rust::Plots::GGPlot::GeomCol.new(*arguments, **options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def geom_bar(*arguments, **options)
|
69
|
+
return Rust::Plots::GGPlot::GeomBar.new(*arguments, **options)
|
70
|
+
end
|
71
|
+
|
72
|
+
def geom_boxplot(*arguments, **options)
|
73
|
+
return Rust::Plots::GGPlot::GeomBoxplot.new(*arguments, **options)
|
74
|
+
end
|
75
|
+
|
76
|
+
def geom_histogram(*arguments, **options)
|
77
|
+
return Rust::Plots::GGPlot::GeomHistogram.new(*arguments, **options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def geom_density(*arguments, **options)
|
81
|
+
return Rust::Plots::GGPlot::GeomDensity.new(*arguments, **options)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require_relative 'core'
|
2
|
+
|
3
|
+
GGPLOT_EXAMPLES = {}
|
4
|
+
|
5
|
+
GGPLOT_EXAMPLES[["Quick introduction", /intro/]] = <<-EOS
|
6
|
+
bind_ggplot! # Avoid using long module names to reach Rust::Plots::GGPlot (simply includes this module)
|
7
|
+
|
8
|
+
# Best with a dataframe, but not necessary. If you have it...
|
9
|
+
df = Rust.toothgrowth
|
10
|
+
plot = PlotBuilder.for_dataframe(df). # Use a dataframe (symbols will be variable names)
|
11
|
+
labeled("Example plot"). # "labeled" sets the label to the last set aesthetic item (x, y, or title, in this case)
|
12
|
+
with_x(:len).labeled("X data from df"). # Set all the aesthetics (x, y, ...)
|
13
|
+
with_y(:dose).labeled("Y data from df").
|
14
|
+
draw_points. # Set the geometries to plot (based on the plot type)
|
15
|
+
build # Returns the plot ready to use
|
16
|
+
plot.show # Show the plot in a window
|
17
|
+
plot.save("output.pdf", width: 5, height: 4) # Save the plot, width, height etc. are optional
|
18
|
+
|
19
|
+
# If you don't have a dataframe...
|
20
|
+
plot2 = PlotBuilder.new.
|
21
|
+
with_x([1,2,3]).labeled("X data from df").
|
22
|
+
with_y([3,4,5]).labeled("Y data from df").
|
23
|
+
draw_points.
|
24
|
+
build
|
25
|
+
plot2.show
|
26
|
+
EOS
|
27
|
+
|
28
|
+
GGPLOT_EXAMPLES[["Scatter plots", /scatter/]] = <<-EOS
|
29
|
+
bind_ggplot!
|
30
|
+
df = Rust.toothgrowth
|
31
|
+
plot = PlotBuilder.for_dataframe(df).
|
32
|
+
with_x(:len).labeled("X data").
|
33
|
+
with_y(:dose).labeled("Y data").
|
34
|
+
draw_points. # To draw points
|
35
|
+
draw_lines. # To draw lines (keep both to draw both)
|
36
|
+
build
|
37
|
+
plot.show
|
38
|
+
EOS
|
39
|
+
|
40
|
+
GGPLOT_EXAMPLES[["Bar plots", /bar/]] = <<-EOS
|
41
|
+
bind_ggplot!
|
42
|
+
df = Rust.toothgrowth
|
43
|
+
plot = PlotBuilder.for_dataframe(df).
|
44
|
+
with_x(:len).labeled("X data").
|
45
|
+
with_fill(:supp).labeled("Legend"). # Use with_fill or with_color for stacked plots
|
46
|
+
draw_bars. # To draw bars
|
47
|
+
build
|
48
|
+
plot.show
|
49
|
+
EOS
|
50
|
+
|
51
|
+
GGPLOT_EXAMPLES[["Box plots", /box/]] = <<-EOS
|
52
|
+
bind_ggplot!
|
53
|
+
df = Rust.toothgrowth
|
54
|
+
plot = PlotBuilder.for_dataframe(df).
|
55
|
+
with_y(:len).labeled("Data to boxplot").
|
56
|
+
with_group(:supp).labeled("Groups"). # Groups to plot
|
57
|
+
draw_boxplot.
|
58
|
+
build
|
59
|
+
plot.show
|
60
|
+
EOS
|
61
|
+
|
62
|
+
GGPLOT_EXAMPLES[["Histograms", /hist/]] = <<-EOS
|
63
|
+
bind_ggplot!
|
64
|
+
df = Rust.toothgrowth
|
65
|
+
plot = PlotBuilder.for_dataframe(df).
|
66
|
+
with_x(:len).labeled("Data to plot").
|
67
|
+
with_fill(:supp).labeled("Color"). # Use with_fill or with_color for multiple plots
|
68
|
+
draw_histogram.
|
69
|
+
build
|
70
|
+
plot.show
|
71
|
+
EOS
|
72
|
+
|
73
|
+
GGPLOT_EXAMPLES[["Themes", /them/]] = <<-EOS
|
74
|
+
bind_ggplot!
|
75
|
+
df = Rust.toothgrowth
|
76
|
+
# The method with_theme allows to change theme options. The method can be called
|
77
|
+
# several times, each time the argument does not overwrite the previous options,
|
78
|
+
# unless they are specified again (in that case, the last specified ones win).
|
79
|
+
plot = PlotBuilder.for_dataframe(df).
|
80
|
+
with_x(:len).labeled("X data").
|
81
|
+
with_y(:dose).labeled("Y data").
|
82
|
+
draw_points.
|
83
|
+
with_theme(
|
84
|
+
ThemeBuilder.new('bw').
|
85
|
+
title(face: 'bold', size: 12). # Each method sets the property for the related element
|
86
|
+
legend do |legend| # Legend and other parts can be set like this
|
87
|
+
legend.position(:left) # Puts the legend on the left
|
88
|
+
end.
|
89
|
+
axis do |axis| # Modifies the axes
|
90
|
+
axis.line(Theme::BlankElement.new) # Hides the lines for the axes
|
91
|
+
axis.text_x(size: 3) # X axis labels
|
92
|
+
end.
|
93
|
+
panel do |panel|
|
94
|
+
panel.grid_major(colour: 'grey70', size: 0.2) # Sets the major ticks grid
|
95
|
+
panel.grid_minor(Theme::BlankElement.new) # Hides the minor ticks grid
|
96
|
+
end.
|
97
|
+
build
|
98
|
+
).build
|
99
|
+
plot.show
|
100
|
+
EOS
|
101
|
+
|
102
|
+
module Rust::Plots::GGPlot
|
103
|
+
def self.help!(topic = nil)
|
104
|
+
unless topic
|
105
|
+
puts "Topics:"
|
106
|
+
GGPLOT_EXAMPLES.keys.each do |key, matcher|
|
107
|
+
puts "- #{key}"
|
108
|
+
end
|
109
|
+
puts "Call again specifying the topic of interest."
|
110
|
+
else
|
111
|
+
GGPLOT_EXAMPLES.each do |key, value|
|
112
|
+
if topic.match(key[1])
|
113
|
+
puts "*** #{key[0]} ***"
|
114
|
+
puts value
|
115
|
+
return
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
puts "Topic not found"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require_relative 'core'
|
2
|
+
|
3
|
+
Rust.prerequisite("ggplot2")
|
4
|
+
|
5
|
+
module Rust::Plots::GGPlot
|
6
|
+
class PlotBuilder
|
7
|
+
def self.for_dataframe(data_frame)
|
8
|
+
return PlotBuilder.new(data_frame)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(data=nil)
|
12
|
+
@data = data
|
13
|
+
|
14
|
+
@aes_options = {}
|
15
|
+
@label_options = {}
|
16
|
+
|
17
|
+
@current_context = :title
|
18
|
+
|
19
|
+
@layers = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_x(variable, label = nil)
|
23
|
+
variable = variable.to_sym if variable.is_a?(String)
|
24
|
+
|
25
|
+
@aes_options[:x] = variable
|
26
|
+
@current_context = :x
|
27
|
+
|
28
|
+
return self
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_y(variable)
|
32
|
+
variable = variable.to_sym if variable.is_a?(String)
|
33
|
+
|
34
|
+
@aes_options[:y] = variable
|
35
|
+
@current_context = :y
|
36
|
+
|
37
|
+
return self
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_group(variable)
|
41
|
+
variable = variable.to_sym if variable.is_a?(String)
|
42
|
+
|
43
|
+
@aes_options[:group] = variable
|
44
|
+
@current_context = :group
|
45
|
+
|
46
|
+
return self
|
47
|
+
end
|
48
|
+
|
49
|
+
def with_color(variable)
|
50
|
+
variable = variable.to_sym if variable.is_a?(String)
|
51
|
+
|
52
|
+
@aes_options[:color] = variable
|
53
|
+
@current_context = :color
|
54
|
+
|
55
|
+
return self
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_fill(variable)
|
59
|
+
variable = variable.to_sym if variable.is_a?(String)
|
60
|
+
|
61
|
+
@aes_options[:fill] = variable
|
62
|
+
@current_context = :fill
|
63
|
+
|
64
|
+
return self
|
65
|
+
end
|
66
|
+
|
67
|
+
def labeled(value)
|
68
|
+
raise "No context for assigning a label" unless @current_context
|
69
|
+
@label_options[@current_context] = value
|
70
|
+
@current_context = nil
|
71
|
+
|
72
|
+
return self
|
73
|
+
end
|
74
|
+
|
75
|
+
def with_x_label(value)
|
76
|
+
@label_options[:x] = value
|
77
|
+
|
78
|
+
return self
|
79
|
+
end
|
80
|
+
|
81
|
+
def with_y_label(value)
|
82
|
+
@label_options[:y] = value
|
83
|
+
|
84
|
+
return self
|
85
|
+
end
|
86
|
+
|
87
|
+
def with_color_label(value)
|
88
|
+
@label_options[:color] = value
|
89
|
+
|
90
|
+
return self
|
91
|
+
end
|
92
|
+
|
93
|
+
def with_title(value)
|
94
|
+
@label_options[:title] = value
|
95
|
+
|
96
|
+
return self
|
97
|
+
end
|
98
|
+
|
99
|
+
def draw_points(**options)
|
100
|
+
@layers << GeomPoint.new(**options)
|
101
|
+
|
102
|
+
@current_context = nil
|
103
|
+
|
104
|
+
return self
|
105
|
+
end
|
106
|
+
|
107
|
+
def draw_lines(**options)
|
108
|
+
@layers << GeomLine.new(**options)
|
109
|
+
|
110
|
+
@current_context = nil
|
111
|
+
|
112
|
+
return self
|
113
|
+
end
|
114
|
+
|
115
|
+
def draw_bars(**options)
|
116
|
+
@layers << GeomBar.new(**options)
|
117
|
+
|
118
|
+
@current_context = nil
|
119
|
+
|
120
|
+
return self
|
121
|
+
end
|
122
|
+
|
123
|
+
def draw_cols(**options)
|
124
|
+
@layers << GeomCol.new(**options)
|
125
|
+
|
126
|
+
@current_context = nil
|
127
|
+
|
128
|
+
return self
|
129
|
+
end
|
130
|
+
|
131
|
+
def draw_boxplot(**options)
|
132
|
+
@layers << GeomBoxplot.new(**options)
|
133
|
+
|
134
|
+
@current_context = nil
|
135
|
+
|
136
|
+
return self
|
137
|
+
end
|
138
|
+
|
139
|
+
def draw_histogram(**options)
|
140
|
+
@layers << GeomHistogram.new(**options)
|
141
|
+
|
142
|
+
@current_context = nil
|
143
|
+
|
144
|
+
return self
|
145
|
+
end
|
146
|
+
|
147
|
+
def draw_density(**options)
|
148
|
+
@layers << GeomDensity.new(**options)
|
149
|
+
|
150
|
+
@current_context = nil
|
151
|
+
|
152
|
+
return self
|
153
|
+
end
|
154
|
+
|
155
|
+
def with_theme(theme)
|
156
|
+
@layers << theme
|
157
|
+
|
158
|
+
@current_context = nil
|
159
|
+
|
160
|
+
return self
|
161
|
+
end
|
162
|
+
|
163
|
+
def flip_coordinates
|
164
|
+
@layers << FlipCoordinates.new
|
165
|
+
|
166
|
+
@current_context = nil
|
167
|
+
|
168
|
+
return self
|
169
|
+
end
|
170
|
+
|
171
|
+
def build
|
172
|
+
plot = Plot.new(@data, Aes.new(**@aes_options))
|
173
|
+
plot.theme = @theme if @theme
|
174
|
+
plot << @layers if @layers.size > 0
|
175
|
+
if @label_options.size > 0
|
176
|
+
if @label_options.keys.include?(:group)
|
177
|
+
value = @label_options.delete(:group)
|
178
|
+
selected = [:x, :y] - @label_options.keys
|
179
|
+
@label_options[selected.first] = value if selected.size == 1
|
180
|
+
end
|
181
|
+
|
182
|
+
plot << Labels.new(**@label_options)
|
183
|
+
end
|
184
|
+
|
185
|
+
return plot
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,435 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'core'
|
3
|
+
|
4
|
+
module Rust::Plots::GGPlot
|
5
|
+
class Theme < Layer
|
6
|
+
def self.from_h(options)
|
7
|
+
starting = options.delete('_starting')
|
8
|
+
options = options.map do |key, value|
|
9
|
+
if value.is_a?(Hash)
|
10
|
+
case value.delete('_type').split("::").last
|
11
|
+
when 'TextElement'
|
12
|
+
[key, TextElement.new(**value)]
|
13
|
+
when 'RectElement'
|
14
|
+
[key, RectElement.new(**value)]
|
15
|
+
when 'TextElement'
|
16
|
+
[key, TextElement.new(**value)]
|
17
|
+
when 'LineElement'
|
18
|
+
[key, LineElement.new(**value)]
|
19
|
+
end
|
20
|
+
else
|
21
|
+
[key, value]
|
22
|
+
end
|
23
|
+
end.to_h
|
24
|
+
|
25
|
+
return Theme.new(starting, **options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.load(filename)
|
29
|
+
json = JSON.parse(File.read(filename))
|
30
|
+
return self.from_h(json.to_h)
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(starting, **options)
|
34
|
+
super("theme", **options)
|
35
|
+
if starting
|
36
|
+
@starting = "theme_" + starting
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_R
|
41
|
+
result = super do |options, arguments|
|
42
|
+
[
|
43
|
+
options.map { |k, v| [k.to_s.gsub("_", "."), v] }.to_h,
|
44
|
+
arguments
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
result = Rust::Function.new(@starting).to_R + " + " + result if @starting
|
49
|
+
|
50
|
+
return result
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_h
|
54
|
+
options = @options.clone
|
55
|
+
|
56
|
+
options['_starting'] = @starting.sub("theme_", "")
|
57
|
+
options = options.map do |key, value|
|
58
|
+
[key, value.is_a?(Theme::Element) ? value.to_h : value]
|
59
|
+
end.to_h
|
60
|
+
|
61
|
+
return options
|
62
|
+
end
|
63
|
+
|
64
|
+
def save(path, force = false)
|
65
|
+
if !force && FileTest.exist?(path)
|
66
|
+
raise "File already existing."
|
67
|
+
end
|
68
|
+
|
69
|
+
File.open(path, "w") do |f|
|
70
|
+
f.write(
|
71
|
+
JSON.pretty_generate(
|
72
|
+
self.to_h
|
73
|
+
)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Theme::Element
|
82
|
+
attr_reader :options
|
83
|
+
|
84
|
+
def initialize(**options)
|
85
|
+
@options = options
|
86
|
+
end
|
87
|
+
|
88
|
+
def r_function
|
89
|
+
raise "Not implemented for generic theme element"
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_R
|
93
|
+
options = @options.map { |k, v| [k.to_s.gsub("_", "."), v] }.to_h
|
94
|
+
|
95
|
+
function = Rust::Function.new(self.r_function)
|
96
|
+
function.options = Rust::Options.from_hash(options)
|
97
|
+
|
98
|
+
return function.to_R
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_h
|
102
|
+
hash = @options.clone
|
103
|
+
hash['_type'] = self.class.name
|
104
|
+
return hash
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class Theme::TextElement < Theme::Element
|
109
|
+
def r_function
|
110
|
+
return "element_text"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class Theme::LineElement < Theme::Element
|
115
|
+
def r_function
|
116
|
+
return "element_line"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Theme::RectElement < Theme::Element
|
121
|
+
def r_function
|
122
|
+
return "element_rect"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class Theme::BlankElement < Theme::Element
|
127
|
+
def r_function
|
128
|
+
return "element_blank"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class ThemeComponentBuilder
|
133
|
+
def initialize(namespace=nil)
|
134
|
+
@namespace = namespace
|
135
|
+
@options = {}
|
136
|
+
end
|
137
|
+
|
138
|
+
def option(key, value)
|
139
|
+
key = "#@namespace.#{key}" if @namespace
|
140
|
+
@options[key] = value
|
141
|
+
|
142
|
+
return self
|
143
|
+
end
|
144
|
+
|
145
|
+
def [](key)
|
146
|
+
key = "#@namespace.#{key}" if @namespace
|
147
|
+
return @options[key]
|
148
|
+
end
|
149
|
+
|
150
|
+
def line_el(value)
|
151
|
+
if value.is_a?(Theme::LineElement) || value.is_a?(Theme::BlankElement)
|
152
|
+
return value
|
153
|
+
elsif value.is_a?(Hash)
|
154
|
+
return Theme::LineElement.new(**value)
|
155
|
+
else
|
156
|
+
raise "Expected line or hash"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def rect_el(value)
|
161
|
+
if value.is_a?(Theme::RectElement) || value.is_a?(Theme::BlankElement)
|
162
|
+
return value
|
163
|
+
elsif value.is_a?(Hash)
|
164
|
+
return Theme::RectElement.new(**value)
|
165
|
+
else
|
166
|
+
raise "Expected rect or hash"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def text_el(value)
|
171
|
+
if value.is_a?(Theme::TextElement) || value.is_a?(Theme::BlankElement)
|
172
|
+
return value
|
173
|
+
elsif value.is_a?(Hash)
|
174
|
+
return Theme::TextElement.new(**value)
|
175
|
+
else
|
176
|
+
raise "Expected text or hash"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def unit_el(value)
|
181
|
+
numeric = nil
|
182
|
+
unit = nil
|
183
|
+
|
184
|
+
if input.is_a?(String)
|
185
|
+
numeric, unit = *input.scan(/^([0-9.]+)([A-Za-z]+)/).flatten
|
186
|
+
|
187
|
+
raise "Unclear numeric part in #{input}" unless numeric
|
188
|
+
raise "Unclear unit part in #{input}" unless unit
|
189
|
+
elsif input.is_a?(Numeric)
|
190
|
+
numeric = input
|
191
|
+
unit = "npc"
|
192
|
+
end
|
193
|
+
|
194
|
+
raise "Unable to handle #{input}" unless numeric && unit
|
195
|
+
|
196
|
+
function = Rust::Function.new("units")
|
197
|
+
function.arguments = Rust::Arguments.new([numeric, unit])
|
198
|
+
|
199
|
+
return function.to_R
|
200
|
+
end
|
201
|
+
|
202
|
+
def alignment_el(value)
|
203
|
+
if value.is_a?(String) || value.is_a?(Symbol)
|
204
|
+
case value.to_s.downcase
|
205
|
+
when 'left'
|
206
|
+
value = 1
|
207
|
+
when 'right'
|
208
|
+
value = 0
|
209
|
+
else
|
210
|
+
value = 1
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
return value
|
215
|
+
end
|
216
|
+
|
217
|
+
def numeric_el(value)
|
218
|
+
raise "Expected number" unless value.is_a?(Numeric)
|
219
|
+
return value
|
220
|
+
end
|
221
|
+
|
222
|
+
def build
|
223
|
+
@options
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class ThemeBuilder < ThemeComponentBuilder
|
228
|
+
def initialize(starting = 'bw')
|
229
|
+
super("plot")
|
230
|
+
@starting = starting
|
231
|
+
end
|
232
|
+
|
233
|
+
def background(value)
|
234
|
+
self.option('background', rect_el(value))
|
235
|
+
end
|
236
|
+
|
237
|
+
def title(value)
|
238
|
+
self.option('title', text_el(value))
|
239
|
+
end
|
240
|
+
|
241
|
+
def axis
|
242
|
+
builder = ThemeAxisBuilder.new
|
243
|
+
yield builder
|
244
|
+
|
245
|
+
@options.merge!(builder.build)
|
246
|
+
return self
|
247
|
+
end
|
248
|
+
|
249
|
+
def legend
|
250
|
+
builder = ThemeLegendBuilder.new
|
251
|
+
yield builder
|
252
|
+
|
253
|
+
@options.merge!(builder.build)
|
254
|
+
return self
|
255
|
+
end
|
256
|
+
|
257
|
+
def panel
|
258
|
+
builder = ThemePanelBuilder.new
|
259
|
+
yield builder
|
260
|
+
|
261
|
+
@options.merge!(builder.build)
|
262
|
+
return self
|
263
|
+
end
|
264
|
+
|
265
|
+
def build
|
266
|
+
return Theme.new(@starting, **@options)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
class ThemeAxisBuilder < ThemeComponentBuilder
|
271
|
+
def initialize
|
272
|
+
super("axis")
|
273
|
+
end
|
274
|
+
|
275
|
+
def line(value)
|
276
|
+
self.option('line', line_el(value))
|
277
|
+
end
|
278
|
+
|
279
|
+
def text(value)
|
280
|
+
self.option('text', text_el(value))
|
281
|
+
end
|
282
|
+
|
283
|
+
def text_x(value)
|
284
|
+
self.option('text.x', text_el(value))
|
285
|
+
end
|
286
|
+
|
287
|
+
def text_y(value)
|
288
|
+
self.option('text.y', text_el(value))
|
289
|
+
end
|
290
|
+
|
291
|
+
def title(value)
|
292
|
+
self.option('title', text_el(value))
|
293
|
+
end
|
294
|
+
|
295
|
+
def title_x(value)
|
296
|
+
self.option('title.x', text_el(value))
|
297
|
+
end
|
298
|
+
|
299
|
+
def title_y(value)
|
300
|
+
self.option('title.y', text_el(value))
|
301
|
+
end
|
302
|
+
|
303
|
+
def ticks(value)
|
304
|
+
self.option('ticks', line_el(value))
|
305
|
+
end
|
306
|
+
|
307
|
+
def ticks_length(value)
|
308
|
+
self.option('ticks.length', unit_el(value))
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
class ThemeLegendBuilder < ThemeComponentBuilder
|
313
|
+
def initialize
|
314
|
+
super("legend")
|
315
|
+
end
|
316
|
+
|
317
|
+
def position(value)
|
318
|
+
self.option('position', value)
|
319
|
+
end
|
320
|
+
|
321
|
+
def justification(value)
|
322
|
+
self.option('justification', value)
|
323
|
+
end
|
324
|
+
|
325
|
+
def background(value)
|
326
|
+
self.option('background', rect_el(value))
|
327
|
+
end
|
328
|
+
|
329
|
+
def key_background(value)
|
330
|
+
self.option('key', rect_el(value))
|
331
|
+
end
|
332
|
+
|
333
|
+
def key_size(value)
|
334
|
+
self.option('key.size', unit_el(value))
|
335
|
+
end
|
336
|
+
|
337
|
+
def key_height(value)
|
338
|
+
self.option('key.height', unit_el(value))
|
339
|
+
end
|
340
|
+
|
341
|
+
def key_width(value)
|
342
|
+
self.option('key.width', unit_el(value))
|
343
|
+
end
|
344
|
+
|
345
|
+
def margin(value)
|
346
|
+
self.option('margin', unit_el(value))
|
347
|
+
end
|
348
|
+
|
349
|
+
def text(value)
|
350
|
+
self.option('text', text_el(value))
|
351
|
+
end
|
352
|
+
|
353
|
+
def text_align(value)
|
354
|
+
self.option('text.align', alignment_el(value))
|
355
|
+
end
|
356
|
+
|
357
|
+
def title(value)
|
358
|
+
self.option('title', text_el(value))
|
359
|
+
end
|
360
|
+
|
361
|
+
def title_align(value)
|
362
|
+
self.option('key.size', alignment_el(value))
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
class ThemePanelBuilder < ThemeComponentBuilder
|
367
|
+
def initialize
|
368
|
+
super("panel")
|
369
|
+
end
|
370
|
+
|
371
|
+
def background(value)
|
372
|
+
self.option('background', rect_el(value))
|
373
|
+
end
|
374
|
+
|
375
|
+
def border(value)
|
376
|
+
self.option('border', rect_el(value))
|
377
|
+
end
|
378
|
+
|
379
|
+
def grid_major(value)
|
380
|
+
self.option('grid.major', line_el(value))
|
381
|
+
end
|
382
|
+
|
383
|
+
def grid_major_x(value)
|
384
|
+
self.option('grid.major.x', line_el(value))
|
385
|
+
end
|
386
|
+
|
387
|
+
def grid_major_y(value)
|
388
|
+
self.option('grid.major.y', line_el(value))
|
389
|
+
end
|
390
|
+
|
391
|
+
def grid_minor(value)
|
392
|
+
self.option('grid.minor', line_el(value))
|
393
|
+
end
|
394
|
+
|
395
|
+
def grid_minor_x(value)
|
396
|
+
self.option('grid.minor.x', line_el(value))
|
397
|
+
end
|
398
|
+
|
399
|
+
def grid_minor_y(value)
|
400
|
+
self.option('grid.minor.y', line_el(value))
|
401
|
+
end
|
402
|
+
|
403
|
+
def aspect_ratio(value)
|
404
|
+
self.option('aspect.ratio', numeric_el(value))
|
405
|
+
end
|
406
|
+
|
407
|
+
def margin(value)
|
408
|
+
self.option('margin', unit_el(value))
|
409
|
+
end
|
410
|
+
|
411
|
+
def margin_x(value)
|
412
|
+
self.option('margin.x', unit_el(value))
|
413
|
+
end
|
414
|
+
|
415
|
+
def margin_y(value)
|
416
|
+
self.option('margin.y', unit_el(value))
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
self.default_theme = ThemeBuilder.new.
|
421
|
+
title(face: 'bold', size: 12).
|
422
|
+
legend do |legend|
|
423
|
+
legend.background(fill: 'white', size: 4, colour: 'white')
|
424
|
+
legend.position([0, 1])
|
425
|
+
legend.justification([0, 1])
|
426
|
+
end.
|
427
|
+
axis do |axis|
|
428
|
+
axis.ticks(colour: 'grey70', size: 0.2)
|
429
|
+
end.
|
430
|
+
panel do |panel|
|
431
|
+
panel.grid_major(colour: 'grey70', size: 0.2)
|
432
|
+
panel.grid_minor(Theme::BlankElement.new)
|
433
|
+
end.
|
434
|
+
build
|
435
|
+
end
|
data/lib/rust.rb
CHANGED
@@ -2,3 +2,22 @@ require_relative 'rust/core'
|
|
2
2
|
require_relative 'rust/models/all'
|
3
3
|
require_relative 'rust/plots/all'
|
4
4
|
require_relative 'rust/stats/all'
|
5
|
+
|
6
|
+
module Rust
|
7
|
+
@@datasets = {}
|
8
|
+
|
9
|
+
def self.toothgrowth
|
10
|
+
@@datasets[:ToothGrowth] = Rust.exclusive { Rust['ToothGrowth'] } unless @@datasets[:ToothGrowth]
|
11
|
+
return @@datasets[:ToothGrowth]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cars
|
15
|
+
@@datasets[:cars] = Rust.exclusive { Rust['cars'] } unless @@datasets[:cars]
|
16
|
+
return @@datasets[:cars]
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.iris
|
20
|
+
@@datasets[:iris] = Rust.exclusive { Rust['iris'] } unless @@datasets[:iris]
|
21
|
+
return @@datasets[:iris]
|
22
|
+
end
|
23
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rust
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.12'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simone Scalabrino
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rinruby
|
@@ -71,6 +71,12 @@ files:
|
|
71
71
|
- lib/rust/core/types/matrix.rb
|
72
72
|
- lib/rust/core/types/s4class.rb
|
73
73
|
- lib/rust/core/types/utils.rb
|
74
|
+
- lib/rust/external/ggplot2.rb
|
75
|
+
- lib/rust/external/ggplot2/core.rb
|
76
|
+
- lib/rust/external/ggplot2/geoms.rb
|
77
|
+
- lib/rust/external/ggplot2/helper.rb
|
78
|
+
- lib/rust/external/ggplot2/plot_builder.rb
|
79
|
+
- lib/rust/external/ggplot2/themes.rb
|
74
80
|
- lib/rust/external/robustbase.rb
|
75
81
|
- lib/rust/models/all.rb
|
76
82
|
- lib/rust/models/anova.rb
|