plotrb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,232 @@
1
+ module Plotrb
2
+
3
+ # The basic tabular data model used by Vega.
4
+ # See {https://github.com/trifacta/vega/wiki/Data}
5
+ class Data
6
+
7
+ include ::Plotrb::Base
8
+
9
+ # @!attributes name
10
+ # @return [String] the name of the data set
11
+ # @!attributes format
12
+ # @return [Format] the format of the data file
13
+ # @!attributes values
14
+ # @return [Hash, Array, String] the actual data set
15
+ # @!attributes source
16
+ # @return [String, Data] the name of another data set to use as source
17
+ # @!attributes url
18
+ # @return [String] the url from which to load the data set
19
+ # @!attributes transform
20
+ # @return [Array<Transform>] an array of transform definitions
21
+ add_attributes :name, :format, :values, :source, :url, :transform
22
+
23
+ def initialize(&block)
24
+ define_single_val_attributes(:name, :values, :source, :url)
25
+ define_multi_val_attribute(:transform)
26
+ self.singleton_class.class_eval {
27
+ alias_method :file, :url
28
+ }
29
+ self.instance_eval(&block) if block_given?
30
+ ::Plotrb::Kernel.data << self
31
+ self
32
+ end
33
+
34
+ def format(*args, &block)
35
+ case args.size
36
+ when 0
37
+ @format
38
+ when 1
39
+ @format = ::Plotrb::Data::Format.new(args[0].to_sym, &block)
40
+ self
41
+ else
42
+ raise ArgumentError, 'Invalid Data format'
43
+ end
44
+ end
45
+
46
+ def extra_fields
47
+ @extra_fields ||= [:data, :index]
48
+ if @transform
49
+ @extra_fields.concat(@transform.collect { |t| t.extra_fields }).
50
+ flatten!.uniq!
51
+ end
52
+ @extra_fields
53
+ end
54
+
55
+ def method_missing(method, *args, &block)
56
+ case method.to_s
57
+ # set format of the data
58
+ when /^as_(csv|tsv|json|topojson|treejson)$/
59
+ self.format($1.to_sym, &block)
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def attribute_post_processing
68
+ process_name
69
+ process_values
70
+ process_source
71
+ process_url
72
+ process_transform
73
+ end
74
+
75
+ def process_name
76
+ if @name.nil? || @name.strip.empty?
77
+ raise ArgumentError, 'Name missing for Data object'
78
+ end
79
+ if ::Plotrb::Kernel.duplicate_data?(@name)
80
+ raise ArgumentError, 'Duplicate names for Data object'
81
+ end
82
+ end
83
+
84
+ def process_values
85
+ return unless @values
86
+ case @values
87
+ when String
88
+ begin
89
+ Yajl::Parser.parse(@values)
90
+ rescue Yajl::ParseError
91
+ raise ArgumentError, 'Invalid JSON values in Data'
92
+ end
93
+ when Array, Hash
94
+ # leave as it is
95
+ else
96
+ raise ArgumentError, 'Unsupported value type in Data'
97
+ end
98
+ end
99
+
100
+ def process_source
101
+ return unless @source
102
+ case source
103
+ when String
104
+ unless ::Plotrb::Kernel.find_data(@source)
105
+ raise ArgumentError, 'Source Data not found'
106
+ end
107
+ when ::Plotrb::Data
108
+ @source = @source.name
109
+ else
110
+ raise ArgumentError, 'Unknown Data source'
111
+ end
112
+ end
113
+
114
+ def process_url
115
+ return unless @url
116
+ begin
117
+ URI.parse(@url)
118
+ rescue URI::InvalidURIError
119
+ raise ArgumentError, 'Invalid URL for Data'
120
+ end
121
+ end
122
+
123
+ def process_transform
124
+ return unless @transform
125
+ if @transform.any? { |t| not t.is_a?(::Plotrb::Transform) }
126
+ raise ArgumentError, 'Invalid Data Transform'
127
+ end
128
+ end
129
+
130
+ class Format
131
+
132
+ include ::Plotrb::Base
133
+
134
+ add_attributes :type
135
+
136
+ def initialize(type, &block)
137
+ case type
138
+ when :json
139
+ add_attributes(:parse, :property)
140
+ define_single_val_attributes(:parse, :property)
141
+ when :csv, :tsv
142
+ add_attributes(:parse)
143
+ define_single_val_attribute(:parse)
144
+ when :topojson
145
+ add_attributes(:feature, :mesh)
146
+ define_single_val_attributes(:feature, :mesh)
147
+ when :treejson
148
+ add_attributes(:parse, :children)
149
+ define_single_val_attributes(:parse, :children)
150
+ else
151
+ raise ArgumentError, 'Invalid Data format'
152
+ end
153
+ @type = type
154
+ self.instance_eval(&block) if block_given?
155
+ self
156
+ end
157
+
158
+ def date(*field, &block)
159
+ @parse ||= {}
160
+ field.flatten.each { |f| @parse.merge!(f => :date) }
161
+ self.instance_eval(&block) if block_given?
162
+ self
163
+ end
164
+ alias_method :as_date, :date
165
+
166
+ def number(*field, &block)
167
+ @parse ||= {}
168
+ field.flatten.each { |f| @parse.merge!(f => :number) }
169
+ self.instance_eval(&block) if block_given?
170
+ self
171
+ end
172
+ alias_method :as_number, :number
173
+
174
+ def boolean(*field, &block)
175
+ @parse ||= {}
176
+ field.flatten.each { |f| @parse.merge!(f => :boolean) }
177
+ self.instance_eval(&block) if block_given?
178
+ self
179
+ end
180
+ alias_method :as_boolean, :boolean
181
+
182
+ private
183
+
184
+ def attribute_post_processing
185
+ process_parse
186
+ process_property
187
+ process_feature
188
+ process_mesh
189
+ process_children
190
+ end
191
+
192
+ def process_parse
193
+ return unless @parse
194
+ valid_type = %i(number boolean date)
195
+ unless @parse.is_a?(Hash) && (@parse.values - valid_type).empty?
196
+ raise ArgumentError, 'Invalid parse options for Data format'
197
+ end
198
+ end
199
+
200
+ def process_property
201
+ return unless @property
202
+ unless @property.is_a?(String)
203
+ raise ArgumentError, 'Invalid JSON property'
204
+ end
205
+ end
206
+
207
+ def process_feature
208
+ return unless @feature
209
+ unless @feature.is_a?(String)
210
+ raise ArgumentError, 'Invalid TopoJSON feature'
211
+ end
212
+ end
213
+
214
+ def process_mesh
215
+ return unless @mesh
216
+ unless @mesh.is_a?(String)
217
+ raise ArgumentError, 'Invalid TopoJSON mesh'
218
+ end
219
+ end
220
+
221
+ def process_children
222
+ return unless @children
223
+ unless @children.is_a?(String)
224
+ raise ArgumentError, 'Invalid TreeJSON children'
225
+ end
226
+ end
227
+
228
+ end
229
+
230
+ end
231
+
232
+ end
@@ -0,0 +1,136 @@
1
+ module Plotrb
2
+
3
+ # Kernel module includes most of the shortcuts used in Plotrb
4
+ module Kernel
5
+
6
+ # a global space keeping track of all Data objects defined
7
+ def self.data
8
+ @data ||= []
9
+ end
10
+
11
+ # @return [Data] find Data object by name
12
+ def self.find_data(name)
13
+ @data.find { |d| d.name == name.to_s }
14
+ end
15
+
16
+ # @return [Boolean] if a Data object with same name already exists
17
+ def self.duplicate_data?(name)
18
+ @data.select { |d| d.name == name.to_s }.size > 1
19
+ end
20
+
21
+ # a global space keeping track of all Axis objects defined
22
+ def self.axes
23
+ @axes ||= []
24
+ end
25
+
26
+ # a global space keeping track of all Scale objects defined
27
+ def self.scales
28
+ @scales ||= []
29
+ end
30
+
31
+ # @return [Scale] find Scale object by name
32
+ def self.find_scale(name)
33
+ @scales.find { |s| s.name == name.to_s }
34
+ end
35
+
36
+ # @return [Boolean] if a Scale object with same name already exists
37
+ def self.duplicate_scale?(name)
38
+ @scales.select { |s| s.name == name.to_s }.size > 1
39
+ end
40
+
41
+ # a global space keeping track of all Mark objects defined
42
+ def self.marks
43
+ @marks ||= []
44
+ end
45
+
46
+ # @return [Mark] find Mark object by name
47
+ def self.find_mark(name)
48
+ @marks.find { |m| m.name == name.to_s }
49
+ end
50
+
51
+ # @return [Boolean] if a Mark object with same name already exists
52
+ def self.duplicate_mark?(name)
53
+ @marks.select { |m| m.name == name.to_s }.size > 1
54
+ end
55
+
56
+ # a global space keeping track of all Transform objects defined
57
+ def self.transforms
58
+ @transforms ||= []
59
+ end
60
+
61
+ # a global space keeping track of all Transform objects defined
62
+ def self.legends
63
+ @legends ||= []
64
+ end
65
+
66
+ # Initialize ::Plotrb::Visualization object
67
+
68
+ def visualization(&block)
69
+ ::Plotrb::Visualization.new(&block)
70
+ end
71
+
72
+ # Initialize ::Plotrb::Data objects
73
+
74
+ def pdata(&block)
75
+ ::Plotrb::Data.new(&block)
76
+ end
77
+
78
+ def legend(&block)
79
+ ::Plotrb::Legend.new(&block)
80
+ end
81
+
82
+ def method_missing(method, *args, &block)
83
+ case method.to_s
84
+ when /^(\w)_axis$/
85
+ # Initialize ::Plotrb::Axis objects
86
+ if ::Plotrb::Axis::TYPES.include?($1.to_sym)
87
+ cache_method($1, 'axis')
88
+ self.send(method)
89
+ else
90
+ super
91
+ end
92
+ when /^(\w+)_scale$/
93
+ # Initialize ::Plotrb::Scale objects
94
+ if ::Plotrb::Scale::TYPES.include?($1.to_sym)
95
+ cache_method($1, 'scale')
96
+ self.send(method)
97
+ else
98
+ super
99
+ end
100
+ when /^(\w+)_transform$/
101
+ # Initialize ::Plotrb::Transform objects
102
+ if ::Plotrb::Transform::TYPES.include?($1.to_sym)
103
+ cache_method($1, 'transform')
104
+ self.send(method)
105
+ else
106
+ super
107
+ end
108
+ when /^(\w+)_mark$/
109
+ # Initialize ::Plotrb::Mark objects
110
+ if ::Plotrb::Mark::TYPES.include?($1.to_sym)
111
+ cache_method($1, 'mark')
112
+ self.send(method)
113
+ else
114
+ super
115
+ end
116
+ else
117
+ super
118
+ end
119
+ end
120
+
121
+ protected
122
+
123
+ def cache_method(type, klass)
124
+ self.class.class_eval {
125
+ define_method("#{type}_#{klass}") do |&block|
126
+ # class names are constants
127
+ # create shortcut methods to initialize Plotrb objects
128
+ ::Kernel::const_get("::Plotrb::#{klass.capitalize}").
129
+ new(type.to_sym, &block)
130
+ end
131
+ }
132
+ end
133
+
134
+ end
135
+
136
+ end
@@ -0,0 +1,168 @@
1
+ module Plotrb
2
+
3
+ # Legends visualize scales. Legends aid interpretation of scales with ranges
4
+ # such as colors, shapes and sizes.
5
+ # See {https://github.com/trifacta/vega/wiki/Legends}
6
+ class Legend
7
+
8
+ include ::Plotrb::Base
9
+
10
+ # @!attribute size
11
+ # @return [Symbol] the name of the scale that determines an item's size
12
+ # @!attribute shape
13
+ # @return [Symbol] the name of the scale that determines an item's shape
14
+ # @!attribute fill
15
+ # @return [Symbol] the name of the scale that determines an item's fill color
16
+ # @!attribute stroke
17
+ # @return [Symbol] the name of the scale that determines an item's stroke color
18
+ # @!attribute orient
19
+ # @return [Symbol] the orientation of the legend
20
+ # @!attribute title
21
+ # @return [Symbol] the title for the legend
22
+ # @!attribute format
23
+ # @return [String] an optional formatting pattern for legend labels
24
+ # @!attribute offset
25
+ # @return [Integer] the offset of the legend
26
+ # @!attribute values
27
+ # @return [Array] explicitly set the visible legend values
28
+ # @!attributes properties
29
+ # @return [MarkProperty] the property set definitions
30
+ LEGEND_PROPERTIES = [:size, :shape, :fill, :stroke, :orient, :title,
31
+ :format, :offset, :values, :properties]
32
+
33
+ add_attributes *LEGEND_PROPERTIES
34
+
35
+ def initialize(&block)
36
+ define_single_val_attributes(:size, :shape, :fill, :stroke, :orient,
37
+ :title, :format, :offset)
38
+ define_multi_val_attributes(:values)
39
+ self.singleton_class.class_eval {
40
+ alias_method :name, :title
41
+ alias_method :offset_by, :offset
42
+ }
43
+ self.instance_eval(&block) if block_given?
44
+ ::Plotrb::Kernel.legends << self
45
+ self
46
+ end
47
+
48
+ def properties(element=nil, &block)
49
+ @properties ||= {}
50
+ return @properties unless element
51
+ @properties.merge!(
52
+ element.to_sym => ::Plotrb::Mark::MarkProperty.new(:text, &block)
53
+ )
54
+ self
55
+ end
56
+
57
+ def method_missing(method, *args, &block)
58
+ case method.to_s
59
+ when /^at_(left|right)$/ # set orient of the legend
60
+ self.orient($1.to_sym, &block)
61
+ when /^with_(\d+)_name/ # set the title of the legend
62
+ self.title($1.to_s, &block)
63
+ else
64
+ super
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def attribute_post_processing
71
+ process_orient
72
+ process_format
73
+ process_properties
74
+ process_size
75
+ process_shape
76
+ process_fill
77
+ process_stroke
78
+ end
79
+
80
+ def process_orient
81
+ return unless @orient
82
+ unless %i(left right).include?(@orient.to_sym)
83
+ raise ArgumentError, 'Invalid Axis orient'
84
+ end
85
+ end
86
+
87
+ def process_format
88
+ return unless @format
89
+ # D3's format specifier has general form:
90
+ # [​[fill]align][sign][symbol][0][width][,][.precision][type]
91
+ # the regex is taken from d3/src/format/format.js
92
+ re =
93
+ /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i
94
+ @format = @format.to_s
95
+ if @format =~ re
96
+ if "#{$1}#{$2}#{$3}#{$4}#{$5}#{$6}#{$7}#{$8}#{$9}" != @format
97
+ raise ArgumentError, 'Invalid format specifier'
98
+ end
99
+ end
100
+ end
101
+
102
+ def process_size
103
+ return unless @size
104
+ case @size
105
+ when String
106
+ unless ::Plotrb::Kernel.find_scale(@size)
107
+ raise ArgumentError, 'Scale not found'
108
+ end
109
+ when ::Plotrb::Scale
110
+ @size = @size.name
111
+ else
112
+ raise ArgumentError, 'Unknown Scale'
113
+ end
114
+ end
115
+
116
+ def process_shape
117
+ return unless @shape
118
+ case @shape
119
+ when String
120
+ unless ::Plotrb::Kernel.find_scale(@shape)
121
+ raise ArgumentError, 'Scale not found'
122
+ end
123
+ when ::Plotrb::Scale
124
+ @shape = @shape.name
125
+ else
126
+ raise ArgumentError, 'Unknown Scale'
127
+ end
128
+ end
129
+
130
+ def process_fill
131
+ return unless @fill
132
+ case @fill
133
+ when String
134
+ unless ::Plotrb::Kernel.find_scale(@fill)
135
+ raise ArgumentError, 'Scale not found'
136
+ end
137
+ when ::Plotrb::Scale
138
+ @fill = @fill.name
139
+ else
140
+ raise ArgumentError, 'Unknown Scale'
141
+ end
142
+ end
143
+
144
+ def process_stroke
145
+ return unless @stroke
146
+ case @stroke
147
+ when String
148
+ unless ::Plotrb::Kernel.find_scale(@stroke)
149
+ raise ArgumentError, 'Scale not found'
150
+ end
151
+ when ::Plotrb::Scale
152
+ @stroke = @stroke.name
153
+ else
154
+ raise ArgumentError, 'Unknown Scale'
155
+ end
156
+ end
157
+
158
+
159
+ def process_properties
160
+ return unless @properties
161
+ valid_elements = %i(title labels symbols gradient legend)
162
+ unless (@properties.keys - valid_elements).empty?
163
+ raise ArgumentError, 'Invalid property element'
164
+ end
165
+ end
166
+
167
+ end
168
+ end