plotrb 0.0.1
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 +7 -0
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +38 -0
- data/README.rdoc +77 -0
- data/Rakefile +7 -0
- data/examples/arc.rb +31 -0
- data/examples/area.rb +48 -0
- data/examples/bar.rb +44 -0
- data/examples/barley.rb +66 -0
- data/examples/choropleth.rb +48 -0
- data/examples/lifelines.rb +106 -0
- data/examples/scatter.rb +43 -0
- data/lib/plotrb.rb +25 -0
- data/lib/plotrb/axes.rb +208 -0
- data/lib/plotrb/base.rb +193 -0
- data/lib/plotrb/data.rb +232 -0
- data/lib/plotrb/kernel.rb +136 -0
- data/lib/plotrb/legends.rb +168 -0
- data/lib/plotrb/marks.rb +459 -0
- data/lib/plotrb/scales.rb +346 -0
- data/lib/plotrb/simple.rb +197 -0
- data/lib/plotrb/transforms.rb +592 -0
- data/lib/plotrb/version.rb +3 -0
- data/lib/plotrb/visualization.rb +55 -0
- data/plotrb.gemspec +27 -0
- data/spec/plotrb/axes_spec.rb +227 -0
- data/spec/plotrb/base_spec.rb +321 -0
- data/spec/plotrb/data_spec.rb +258 -0
- data/spec/plotrb/kernel_spec.rb +54 -0
- data/spec/plotrb/legends_spec.rb +157 -0
- data/spec/plotrb/marks_spec.rb +46 -0
- data/spec/plotrb/scales_spec.rb +187 -0
- data/spec/plotrb/simple_spec.rb +61 -0
- data/spec/plotrb/transforms_spec.rb +248 -0
- data/spec/plotrb/visualization_spec.rb +93 -0
- data/spec/plotrb_spec.rb +5 -0
- data/spec/spec_helper.rb +12 -0
- metadata +180 -0
@@ -0,0 +1,346 @@
|
|
1
|
+
module Plotrb
|
2
|
+
|
3
|
+
# Scales are functions that transform a domain of data values to a range of
|
4
|
+
# visual values.
|
5
|
+
# See {https://github.com/trifacta/vega/wiki/Scales}
|
6
|
+
class Scale
|
7
|
+
|
8
|
+
include ::Plotrb::Base
|
9
|
+
|
10
|
+
TYPES = %i(linear log pow sqrt quantile quantize threshold ordinal time utc)
|
11
|
+
|
12
|
+
TYPES.each do |t|
|
13
|
+
define_singleton_method(t) do |&block|
|
14
|
+
::Plotrb::Scale.new(t, &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# @!attributes type
|
19
|
+
# @return [Symbol] the type of the scale
|
20
|
+
SCALE_PROPERTIES = [:name, :type, :domain, :domain_min, :domain_max, :range,
|
21
|
+
:range_min, :range_max, :reverse, :round]
|
22
|
+
|
23
|
+
add_attributes *SCALE_PROPERTIES
|
24
|
+
|
25
|
+
RANGE_LITERALS = %i(width height shapes colors more_colors)
|
26
|
+
TIME_SCALE_NICE = %i(second minute hour day week month year)
|
27
|
+
|
28
|
+
def initialize(type=:linear, &block)
|
29
|
+
@type = type
|
30
|
+
case @type
|
31
|
+
when :ordinal
|
32
|
+
set_ordinal_scale_attributes
|
33
|
+
when :time, :utc
|
34
|
+
set_time_scale_attributes
|
35
|
+
else
|
36
|
+
set_quantitative_scale_attributes
|
37
|
+
end
|
38
|
+
set_common_scale_attributes
|
39
|
+
::Plotrb::Kernel.scales << self
|
40
|
+
self.instance_eval(&block) if block_given?
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def type
|
45
|
+
@type
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(method, *args, &block)
|
49
|
+
case method.to_s
|
50
|
+
when /in_(\w+)s$/ # set @nice for time and utc type, eg. in_seconds
|
51
|
+
if TIME_SCALE_NICE.include?($1.to_sym)
|
52
|
+
self.nice($1.to_sym, &block)
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
56
|
+
when /to_(\w+)$/ # set range literals, eg. to_more_colors
|
57
|
+
if RANGE_LITERALS.include?($1.to_sym)
|
58
|
+
self.range($1.to_sym, &block)
|
59
|
+
else
|
60
|
+
super
|
61
|
+
end
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def set_common_scale_attributes
|
70
|
+
# @!attributes name
|
71
|
+
# @return [String] the name of the scale
|
72
|
+
# @!attributes domain
|
73
|
+
# @return [Array(Numeric, Numeric), Array, String] the domain of the
|
74
|
+
# scale, representing the set of data values
|
75
|
+
# @!attributes domain_min
|
76
|
+
# @return [Numeric, String] the minimum value in the scale domain
|
77
|
+
# @!attributes domain_max
|
78
|
+
# @return [Numeric, String] the maximum value in the scale domain
|
79
|
+
# @!attributes range
|
80
|
+
# @return [Array(Numeric, Numeric), Array, String] the range of the
|
81
|
+
# scale, representing the set of visual values
|
82
|
+
# @!attributes range_min
|
83
|
+
# @return [Numeric, String] the minimum value in the scale range
|
84
|
+
# @!attributes range_max
|
85
|
+
# @return [Numeric, String] the maximum value in the scale range
|
86
|
+
# @!attributes reverse
|
87
|
+
# @return [Boolean] whether flips the scale range
|
88
|
+
# @!attributes round
|
89
|
+
# @return [Boolean] whether rounds numeric output values to integers
|
90
|
+
define_single_val_attributes(:name, :domain, :domain_max, :domain_min,
|
91
|
+
:range, :range_max, :range_min)
|
92
|
+
define_boolean_attributes(:reverse, :round)
|
93
|
+
self.singleton_class.class_eval {
|
94
|
+
alias_method :from, :domain
|
95
|
+
alias_method :to, :range
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def set_ordinal_scale_attributes
|
100
|
+
# @!attributes points
|
101
|
+
# @return [Boolean] whether distributes the ordinal values over a
|
102
|
+
# quantitative range at uniformly spaced points or bands
|
103
|
+
# @!attributes padding
|
104
|
+
# @return [Numeric] the spacing among ordinal elements in the scale range
|
105
|
+
# @!attributes sort
|
106
|
+
# @return [Boolean] whether values in the scale domain will be sorted
|
107
|
+
# according to their natural order
|
108
|
+
add_attributes(:points, :padding, :sort)
|
109
|
+
define_boolean_attributes(:points, :sort)
|
110
|
+
define_single_val_attribute(:padding)
|
111
|
+
define_singleton_method(:bands) do |&block|
|
112
|
+
@points = false
|
113
|
+
self.instance_eval(&block) if block
|
114
|
+
self
|
115
|
+
end
|
116
|
+
define_singleton_method(:bands?) do
|
117
|
+
!@points
|
118
|
+
end
|
119
|
+
self.singleton_class.class_eval {
|
120
|
+
alias_method :as_bands, :bands
|
121
|
+
alias_method :as_bands?, :bands?
|
122
|
+
alias_method :as_points, :points
|
123
|
+
alias_method :as_points?, :points?
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
def set_time_scale_attributes
|
128
|
+
# @!attributes clamp
|
129
|
+
# @return [Boolean] whether clamps values that exceed the data domain
|
130
|
+
# to either to minimum or maximum range value
|
131
|
+
# @!attributes nice
|
132
|
+
# @return [Symbol, Boolean, nil] scale domain in a more human-friendly
|
133
|
+
# value range
|
134
|
+
add_attributes(:clamp, :nice)
|
135
|
+
define_boolean_attribute(:clamp)
|
136
|
+
define_single_val_attribute(:nice)
|
137
|
+
end
|
138
|
+
|
139
|
+
def set_quantitative_scale_attributes
|
140
|
+
# @!attributes clamp
|
141
|
+
# @return [Boolean] whether clamps values that exceed the data domain
|
142
|
+
# to either to minimum or maximum range value
|
143
|
+
# @!attributes nice
|
144
|
+
# @return [Boolean] scale domain in a more human-friendly
|
145
|
+
# value range
|
146
|
+
# @!attributes exponent
|
147
|
+
# @return [Numeric] the exponent of the scale transformation
|
148
|
+
# @!attributes zero
|
149
|
+
# @return [Boolean] whether zero baseline value is included
|
150
|
+
add_attributes(:clamp, :exponent, :nice, :zero)
|
151
|
+
define_boolean_attributes(:clamp, :nice, :zero)
|
152
|
+
define_single_val_attribute(:exponent)
|
153
|
+
define_singleton_method(:exclude_zero) do |&block|
|
154
|
+
@zero = false
|
155
|
+
self.instance_eval(&block) if block
|
156
|
+
self
|
157
|
+
end
|
158
|
+
define_singleton_method(:exclude_zero?) do
|
159
|
+
!@zero
|
160
|
+
end
|
161
|
+
self.singleton_class.class_eval {
|
162
|
+
alias_method :nicely, :nice
|
163
|
+
alias_method :nicely?, :nice?
|
164
|
+
alias_method :include_zero, :zero
|
165
|
+
alias_method :include_zero?, :zero?
|
166
|
+
alias_method :in_exponent, :exponent
|
167
|
+
}
|
168
|
+
end
|
169
|
+
|
170
|
+
def attribute_post_processing
|
171
|
+
process_name
|
172
|
+
process_type
|
173
|
+
process_domain
|
174
|
+
process_domain_min
|
175
|
+
process_domain_max
|
176
|
+
process_range
|
177
|
+
end
|
178
|
+
|
179
|
+
def process_name
|
180
|
+
if @name.nil? || @name.strip.empty?
|
181
|
+
raise ArgumentError, 'Name missing for Scale object'
|
182
|
+
end
|
183
|
+
if ::Plotrb::Kernel.duplicate_scale?(@name)
|
184
|
+
raise ArgumentError, 'Duplicate names for Scale object'
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def process_type
|
189
|
+
unless TYPES.include?(@type)
|
190
|
+
raise ArgumentError, 'Invalid Scale type'
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def process_domain
|
195
|
+
return unless @domain
|
196
|
+
case @domain
|
197
|
+
when String
|
198
|
+
@domain = get_data_ref_from_string(@domain)
|
199
|
+
when ::Plotrb::Data
|
200
|
+
@domain = get_data_ref_from_data(@domain)
|
201
|
+
when Array
|
202
|
+
if @domain.all? { |d| is_data_ref?(d) }
|
203
|
+
fields = @domain.collect { |d| get_data_ref_from_string(d) }
|
204
|
+
@domain = {:fields => fields}
|
205
|
+
else
|
206
|
+
# leave as it is
|
207
|
+
end
|
208
|
+
when ::Plotrb::Scale::DataRef
|
209
|
+
# leave as it is
|
210
|
+
else
|
211
|
+
raise ArgumentError, 'Unsupported Scale domain type'
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def process_domain_min
|
216
|
+
# only for quantitative domain
|
217
|
+
return unless @domain_min && !%i(ordinal time utc).include?(@type)
|
218
|
+
case @domain_min
|
219
|
+
when String
|
220
|
+
@domain_min = get_data_ref_from_string(@domain_min)
|
221
|
+
when ::Plotrb::Data
|
222
|
+
@domain_min = get_data_ref_from_data(@domain_min)
|
223
|
+
when Array
|
224
|
+
if @domain_min.all? { |d| is_data_ref?(d) }
|
225
|
+
fields = @domain_min.collect { |d| get_data_ref_from_string(d) }
|
226
|
+
@domain_min = {:fields => fields}
|
227
|
+
else
|
228
|
+
raise ArgumentError, 'Unsupported Scale domain_min type'
|
229
|
+
end
|
230
|
+
when Numeric
|
231
|
+
# leave as it is
|
232
|
+
else
|
233
|
+
raise ArgumentError, 'Unsupported Scale domain_min type'
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def process_domain_max
|
238
|
+
# only for quantitative domain
|
239
|
+
return unless @domain_max && !%i(ordinal time utc).include?(@type)
|
240
|
+
case @domain_max
|
241
|
+
when String
|
242
|
+
@domain_max = get_data_ref_from_string(@domain_max)
|
243
|
+
when ::Plotrb::Data
|
244
|
+
@domain_max = get_data_ref_from_data(@domain_max)
|
245
|
+
when Array
|
246
|
+
if @domain_max.all? { |d| is_data_ref?(d) }
|
247
|
+
fields = @domain_max.collect { |d| get_data_ref_from_string(d) }
|
248
|
+
@domain_max = {:fields => fields}
|
249
|
+
else
|
250
|
+
raise ArgumentError, 'Unsupported Scale domain_max type'
|
251
|
+
end
|
252
|
+
when Numeric
|
253
|
+
# leave as it is
|
254
|
+
else
|
255
|
+
raise ArgumentError, 'Unsupported Scale domain_max type'
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def get_data_ref_from_string(ref)
|
260
|
+
source, field = ref.split('.', 2)
|
261
|
+
data = ::Plotrb::Kernel.find_data(source)
|
262
|
+
if field.nil?
|
263
|
+
if data && data.values.is_a?(Array)
|
264
|
+
::Plotrb::Scale::DataRef.new.data(source).field('data')
|
265
|
+
else
|
266
|
+
::Plotrb::Scale::DataRef.new.data(source).field('index')
|
267
|
+
end
|
268
|
+
elsif field == 'index'
|
269
|
+
::Plotrb::Scale::DataRef.new.data(source).field('index')
|
270
|
+
else
|
271
|
+
if data.extra_fields.include?(field.to_sym)
|
272
|
+
::Plotrb::Scale::DataRef.new.data(source).field(field)
|
273
|
+
else
|
274
|
+
::Plotrb::Scale::DataRef.new.data(source).field("data.#{field}")
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def get_data_ref_from_data(data)
|
280
|
+
if data.values.is_a?(Array)
|
281
|
+
::Plotrb::Scale::DataRef.new.data(data.name).field('data')
|
282
|
+
else
|
283
|
+
::Plotrb::Scale::DataRef.new.data(data.name).field('index')
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def is_data_ref?(ref)
|
288
|
+
return false unless ref.is_a?(String)
|
289
|
+
source, _ = ref.split('.', 2)
|
290
|
+
not ::Plotrb::Kernel.find_data(source).nil?
|
291
|
+
end
|
292
|
+
|
293
|
+
def process_range
|
294
|
+
return unless @range
|
295
|
+
case @range
|
296
|
+
when String, Symbol
|
297
|
+
@range = range_literal(@range)
|
298
|
+
when Array
|
299
|
+
#leave as it is
|
300
|
+
else
|
301
|
+
raise ArgumentError, 'Unsupported Scale range type'
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def range_literal(literal)
|
306
|
+
case literal
|
307
|
+
when :colors
|
308
|
+
:category10
|
309
|
+
when :more_colors
|
310
|
+
:category20
|
311
|
+
when :width, :height, :shapes, :category10, :category20
|
312
|
+
literal
|
313
|
+
else
|
314
|
+
raise ArgumentError, 'Invalid Scale range'
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
# A data reference specifies the field for a given scale property
|
319
|
+
class DataRef
|
320
|
+
|
321
|
+
include ::Plotrb::Base
|
322
|
+
|
323
|
+
# @!attributes data
|
324
|
+
# @return [String] the name of a data set
|
325
|
+
# @!attributes field
|
326
|
+
# @return [String] A field from which to pull a data values
|
327
|
+
add_attributes :data, :field
|
328
|
+
|
329
|
+
# TODO: Support group
|
330
|
+
def initialize(&block)
|
331
|
+
define_single_val_attributes(:data, :field)
|
332
|
+
self.instance_eval(&block) if block
|
333
|
+
self
|
334
|
+
end
|
335
|
+
|
336
|
+
private
|
337
|
+
|
338
|
+
def attribute_post_processing
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
|
344
|
+
end
|
345
|
+
|
346
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
#--
|
2
|
+
# simple.rb: Shortcuts for making some simple plots.
|
3
|
+
# Copyright (c) 2013 Colin J. Fuller and the Ruby Science Foundation
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions
|
8
|
+
# are met:
|
9
|
+
# - Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# - Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
14
|
+
# documentation and/or other materials provided with the
|
15
|
+
# distribution.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
18
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
19
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
20
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
21
|
+
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
22
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
23
|
+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
24
|
+
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
25
|
+
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
26
|
+
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
27
|
+
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
28
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
#++
|
30
|
+
|
31
|
+
require 'plotrb'
|
32
|
+
|
33
|
+
module Plotrb
|
34
|
+
module Simple
|
35
|
+
|
36
|
+
SCATTER_DATA_NAME = 'scatter'
|
37
|
+
SCATTER_X_SCALE_NAME = 'scatter_x'
|
38
|
+
SCATTER_Y_SCALE_NAME = 'scatter_y'
|
39
|
+
|
40
|
+
#
|
41
|
+
# Data name used by scatter plot
|
42
|
+
#
|
43
|
+
def self.scatter_data_name
|
44
|
+
SCATTER_DATA_NAME
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Scale name used by scatter plot for x axis
|
49
|
+
#
|
50
|
+
def self.scatter_x_scale_name
|
51
|
+
SCATTER_X_SCALE_NAME
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Scale name used by scatter plot for y axis
|
56
|
+
#
|
57
|
+
def self.scatter_y_scale_name
|
58
|
+
SCATTER_Y_SCALE_NAME
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Generate a simple 2d scatter plot.
|
63
|
+
#
|
64
|
+
# @param [NMatrix, Array] x the x datapoints; if a single row, will be used
|
65
|
+
# for all y dataseries, if multiple rows, each row of x will be used for
|
66
|
+
# the corresponding row of y
|
67
|
+
# @param [NMatrix, Array] y the y datapoints. Can be a single dimensional array,
|
68
|
+
# or 2D with multiple series in rows; column dimension should match the
|
69
|
+
# number of elements in x.
|
70
|
+
# @param [String, Array<String>] symbol the type of symbol to be used to
|
71
|
+
# plot the points. Can be any symbol Vega understands: circle, square,
|
72
|
+
# cross, diamond, triangle-up, triangle-down. If a single String is
|
73
|
+
# provided, this will be used for all the points. If an Array of Strings
|
74
|
+
# is provided, each symbol in the array will be used for the corresponding
|
75
|
+
# data series (row) in y. Default: 'circle'
|
76
|
+
# @param [String, Array<String>] color the color to be used to plot the
|
77
|
+
# points. If a single String is provided, this will be used for all the
|
78
|
+
# points. If an Array of Strings is provided, each color in the array will
|
79
|
+
# be used for the corresponding data series (row) in y. Default: 'blue'
|
80
|
+
# @param [Numeric] markersize the size of the marker in pixels. Default: 20
|
81
|
+
# @param [Numeric] width the visualization width in pixels. Default: 640
|
82
|
+
# @param [Numeric] height the visualization height in pixels. Default: 480
|
83
|
+
# @param [Array, String] domain the domain for the plot (limits on the
|
84
|
+
# x-axis). This can be a 2-element array of bounds or any other object
|
85
|
+
# that Plotrb::Scale::from understands. Default: scale to x data
|
86
|
+
# @param [Array, String] range the range for the plot (limits on the
|
87
|
+
# y-axis). This can be a 2-element array of bounds or any other object
|
88
|
+
# that Plotrb::Scale::from understands. Default: scale to first row of
|
89
|
+
# y.
|
90
|
+
#
|
91
|
+
# @return [Plotrb::Visualization] A visualization object. (This can be
|
92
|
+
# written to a json string for Vega with #generate_spec.)
|
93
|
+
#
|
94
|
+
# method signature for ruby 2.0 kwargs:
|
95
|
+
# def scatter(x, y, symbol: 'circle', color: 'blue', markersize: 20,
|
96
|
+
# width: 640, height: 480, domain: nil, range: nil)
|
97
|
+
def self.scatter(x, y, kwargs={})
|
98
|
+
kwargs = {symbol: 'circle', color: 'blue', markersize: 20, width: 640,
|
99
|
+
height: 480, domain: nil, range: nil}.merge(kwargs)
|
100
|
+
symbol = kwargs[:symbol]
|
101
|
+
color = kwargs[:color]
|
102
|
+
markersize = kwargs[:markersize]
|
103
|
+
width = kwargs[:width]
|
104
|
+
height = kwargs[:height]
|
105
|
+
domain = kwargs[:domain]
|
106
|
+
range = kwargs[:range]
|
107
|
+
|
108
|
+
datapoints = []
|
109
|
+
n_sets = 1
|
110
|
+
x_n_sets = 1
|
111
|
+
x_size = x.size
|
112
|
+
|
113
|
+
if x.respond_to?(:shape) and x.shape.length > 1 then # x is 2D NMatrix
|
114
|
+
x_n_sets = x.shape[0]
|
115
|
+
x_size = x.shape[1]
|
116
|
+
elsif x.instance_of? Array and x[0].instance_of? Array then # x is nested Array
|
117
|
+
x_n_sets = x.size
|
118
|
+
x_size = x[0].size
|
119
|
+
end
|
120
|
+
|
121
|
+
if y.respond_to?(:shape) and y.shape.length > 1 then # y is 2D NMatrix
|
122
|
+
n_sets = y.shape[0]
|
123
|
+
elsif y.instance_of? Array and y[0].instance_of? Array then # y is nested array
|
124
|
+
n_sets = y.size
|
125
|
+
end
|
126
|
+
|
127
|
+
x_size.times do |i|
|
128
|
+
dp = {}
|
129
|
+
n_sets.times do |j|
|
130
|
+
|
131
|
+
xj = j.modulo(x_n_sets)
|
132
|
+
if x.respond_to?(:shape) and x.shape.length > 1 then
|
133
|
+
dp["x#{xj}".to_sym] = x[xj, i]
|
134
|
+
elsif x.instance_of? Array and x[0].instance_of? Array then
|
135
|
+
dp["x#{xj}".to_sym] = x[xj][i]
|
136
|
+
else
|
137
|
+
dp["x#{xj}".to_sym] = x[i]
|
138
|
+
end
|
139
|
+
|
140
|
+
indices = [i]
|
141
|
+
if y.respond_to?(:shape) and y.shape.length > 1 then
|
142
|
+
indices = [j,i]
|
143
|
+
end
|
144
|
+
if y.instance_of? Array and y[0].instance_of? Array then
|
145
|
+
dp["y#{j}".to_sym] = y[j][*indices]
|
146
|
+
else
|
147
|
+
dp["y#{j}".to_sym] = y[*indices]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
datapoints << dp
|
152
|
+
end
|
153
|
+
|
154
|
+
Plotrb::Kernel.data.delete_if { |d| d.name == scatter_data_name }
|
155
|
+
dataset= Plotrb::Data.new.name(scatter_data_name)
|
156
|
+
dataset.values(datapoints)
|
157
|
+
|
158
|
+
domain_in = "#{scatter_data_name}.x0"
|
159
|
+
if domain then
|
160
|
+
domain_in = domain
|
161
|
+
end
|
162
|
+
range_in = "#{scatter_data_name}.y0"
|
163
|
+
if range then
|
164
|
+
range_in = range
|
165
|
+
end
|
166
|
+
|
167
|
+
Plotrb::Kernel.scales.delete_if { |d| d.name == scatter_x_scale_name or d.name == scatter_y_scale_name }
|
168
|
+
|
169
|
+
xs = linear_scale.name(scatter_x_scale_name).from(domain_in).to_width
|
170
|
+
ys = linear_scale.name(scatter_y_scale_name).from(range_in).to_height
|
171
|
+
|
172
|
+
marks = []
|
173
|
+
n_sets.times do |j|
|
174
|
+
marks << symbol_mark.from(dataset) do
|
175
|
+
c_j = color.instance_of?(Array) ? color[j] : color
|
176
|
+
s_j = symbol.instance_of?(Array) ? symbol[j] : symbol
|
177
|
+
x_j = j.modulo(x_n_sets)
|
178
|
+
enter do
|
179
|
+
x_start { scale(xs).from("x#{x_j}") }
|
180
|
+
y_start { scale(ys).from("y#{j}") }
|
181
|
+
size markersize
|
182
|
+
shape s_j
|
183
|
+
fill c_j
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
visualization.width(width).height(height) do
|
189
|
+
data dataset
|
190
|
+
scales xs, ys
|
191
|
+
marks marks
|
192
|
+
axes x_axis.scale(xs), y_axis.scale(ys)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|