plotrb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ require 'plotrb'
2
+
3
+ raw_data = pdata.name('iris').url('iris_data.json')
4
+ xs = linear_scale.name('x').from('iris.sepalWidth').to_width.nicely
5
+ ys = linear_scale.name('y').from('iris.petalLength').to_height.nicely
6
+ cs = ordinal_scale.name('c').from('iris.species').range(["#800", "#080", "#008"])
7
+
8
+ xaxis = x_axis.scale(xs).offset(5).ticks(5).title('Sepal Width')
9
+ yaxis = y_axis.scale(ys).offset(5).ticks(5).title('Petal Length')
10
+
11
+ lgnd = legend.fill(cs).title('Species') do
12
+ properties(:symbols) do
13
+ fill_opacity 0.5
14
+ stroke :transparent
15
+ end
16
+ end
17
+
18
+ mark = symbol_mark.from(raw_data) do
19
+ enter do
20
+ x { scale(xs).field('sepalWidth') }
21
+ y { scale(ys).field('petalLength') }
22
+ fill { scale(cs).field('species') }
23
+ fill_opacity 0.5
24
+ end
25
+ update do
26
+ size 100
27
+ stroke 'transparent'
28
+ end
29
+ hover do
30
+ size 300
31
+ stroke 'white'
32
+ end
33
+ end
34
+
35
+ vis = visualization.name('arc').width(200).height(200) do
36
+ data raw_data
37
+ scales xs, ys, cs
38
+ axes xaxis, yaxis
39
+ legends lgnd
40
+ marks mark
41
+ end
42
+
43
+ puts vis.generate_spec(:pretty)
@@ -0,0 +1,25 @@
1
+ require 'yajl'
2
+ require 'json'
3
+ require 'uri'
4
+
5
+ require_relative 'plotrb/base'
6
+
7
+ require_relative 'plotrb/data'
8
+ require_relative 'plotrb/transforms'
9
+ require_relative 'plotrb/scales'
10
+ require_relative 'plotrb/marks'
11
+ require_relative 'plotrb/axes'
12
+ require_relative 'plotrb/kernel'
13
+ require_relative 'plotrb/visualization'
14
+ require_relative 'plotrb/legends'
15
+ require_relative 'plotrb/simple'
16
+
17
+ module Plotrb
18
+
19
+ end
20
+
21
+ class Object
22
+
23
+ include ::Plotrb::Kernel
24
+
25
+ end
@@ -0,0 +1,208 @@
1
+ module Plotrb
2
+
3
+ # Axes provide axis lines, ticks, and labels to convey how a spatial range
4
+ # represents a data range.
5
+ # See {https://github.com/trifacta/vega/wiki/Axes}
6
+ class Axis
7
+
8
+ include ::Plotrb::Base
9
+
10
+ TYPES = %i(x y)
11
+
12
+ TYPES.each do |t|
13
+ define_singleton_method(t) do |&block|
14
+ ::Plotrb::Axis.new(t, &block)
15
+ end
16
+ end
17
+
18
+ # @!attribute type
19
+ # @return [Symbol] type of the axis, either :x or :y
20
+ # @!attribute scale
21
+ # @return [String] the name of the scale backing the axis
22
+ # @!attribute orient
23
+ # @return [Symbol] the orientation of the axis
24
+ # @!attribute format
25
+ # @return [String] the formatting pattern for axis labels
26
+ # @!attribute ticks
27
+ # @return [Integer] a desired number of ticks
28
+ # @!attribute values
29
+ # @return [Array] explicitly set the visible axis tick values
30
+ # @!attribute subdivide
31
+ # @return [Integer] the number of minor ticks between major ticks
32
+ # @!attribute tick_padding
33
+ # @return [Integer] the padding between ticks and text labels
34
+ # @!attribute tick_size
35
+ # @return [Integer] the size of major, minor, and end ticks
36
+ # @!attribute tick_size_major
37
+ # @return [Integer] the size of major ticks
38
+ # @!attribute tick_size_minor
39
+ # @return [Integer] the size of minor ticks
40
+ # @!attribute tick_size_end
41
+ # @return [Integer] the size of end ticks
42
+ # @!attribute offset
43
+ # @return [Integer] the offset by which to displace the axis from the edge
44
+ # of the enclosing group or data rectangle
45
+ # @!attribute properties
46
+ # @return [Hash] optional mark property definitions for custom styling
47
+ # @!attribute title
48
+ # @return [String] the title for the axis
49
+ # @!attribute tittle_offset
50
+ # @return [Integer] the offset from the axis at which to place the title
51
+ # @!attribute grid
52
+ # @return [Boolean] whether gridlines should be created
53
+ add_attributes :type, :scale, :orient, :format, :ticks, :values, :subdivide,
54
+ :tick_padding, :tick_size, :tick_size_major, :tick_size_minor,
55
+ :tick_size_end, :offset, :layer, :properties, :title,
56
+ :title_offset, :grid
57
+
58
+ def initialize(type, &block)
59
+ @type = type
60
+ define_single_val_attributes(:scale, :orient, :title, :title_offset,
61
+ :format, :ticks, :subdivide, :tick_padding,
62
+ :tick_size, :tick_size_major, :tick_size_end,
63
+ :tick_size_minor, :offset, :layer)
64
+ define_boolean_attribute(:grid)
65
+ define_multi_val_attributes(:values)
66
+ self.singleton_class.class_eval {
67
+ alias_method :from, :scale
68
+ alias_method :offset_title_by, :title_offset
69
+ alias_method :subdivide_by, :subdivide
70
+ alias_method :major_tick_size, :tick_size_major
71
+ alias_method :minor_tick_size, :tick_size_minor
72
+ alias_method :end_tick_size, :tick_size_end
73
+ alias_method :offset_by, :offset
74
+ alias_method :show_grid, :grid
75
+ alias_method :with_grid, :grid
76
+ alias_method :show_grid?, :grid?
77
+ alias_method :with_grid?, :grid?
78
+ }
79
+ self.instance_eval(&block) if block_given?
80
+ ::Plotrb::Kernel.axes << self
81
+ self
82
+ end
83
+
84
+ def type
85
+ @type
86
+ end
87
+
88
+ def above(&block)
89
+ @layer = :front
90
+ self.instance_eval(&block) if block_given?
91
+ self
92
+ end
93
+
94
+ def above?
95
+ @layer == :front
96
+ end
97
+
98
+ def below(&block)
99
+ @layer = :back
100
+ self.instance_eval(&block) if block_given?
101
+ self
102
+ end
103
+
104
+ def below?
105
+ @layer == :back
106
+ end
107
+
108
+ def no_grid(&block)
109
+ @grid = false
110
+ self.instance_eval(&block) if block
111
+ self
112
+ end
113
+
114
+ def properties(element=nil, &block)
115
+ @properties ||= {}
116
+ return @properties unless element
117
+ @properties.merge!(
118
+ element.to_sym => ::Plotrb::Mark::MarkProperty.new(:text, &block)
119
+ )
120
+ self
121
+ end
122
+
123
+ def method_missing(method, *args, &block)
124
+ case method.to_s
125
+ when /^with_(\d+)_ticks$/ # set number of ticks. eg. in_20_ticks
126
+ self.ticks($1.to_i, &block)
127
+ when /^subdivide_by_(\d+)$/ # set subdivide of ticks
128
+ self.subdivide($1.to_i, &block)
129
+ when /^at_(top|bottom|left|right)$/ # set orient of the axis
130
+ self.orient($1.to_sym, &block)
131
+ when /^in_(front|back)$/ # set layer of the axis
132
+ self.layer($1.to_sym, &block)
133
+ else
134
+ super
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def attribute_post_processing
141
+ process_type
142
+ process_scale
143
+ process_orient
144
+ process_format
145
+ process_layer
146
+ process_properties
147
+ end
148
+
149
+ def process_type
150
+ unless TYPES.include?(@type)
151
+ raise ArgumentError, 'Invalid Axis type'
152
+ end
153
+ end
154
+
155
+ def process_scale
156
+ return unless @scale
157
+ case @scale
158
+ when String
159
+ unless ::Plotrb::Kernel.find_scale(@scale)
160
+ raise ArgumentError, 'Scale not found'
161
+ end
162
+ when ::Plotrb::Scale
163
+ @scale = @scale.name
164
+ else
165
+ raise ArgumentError, 'Unknown Scale'
166
+ end
167
+ end
168
+
169
+ def process_orient
170
+ return unless @orient
171
+ unless %i(top bottom left right).include?(@orient.to_sym)
172
+ raise ArgumentError, 'Invalid Axis orient'
173
+ end
174
+ end
175
+
176
+ def process_format
177
+ return unless @format
178
+ # D3's format specifier has general form:
179
+ # [​[fill]align][sign][symbol][0][width][,][.precision][type]
180
+ # the regex is taken from d3/src/format/format.js
181
+ re =
182
+ /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i
183
+ @format = @format.to_s
184
+ if @format =~ re
185
+ if "#{$1}#{$2}#{$3}#{$4}#{$5}#{$6}#{$7}#{$8}#{$9}" != @format
186
+ raise ArgumentError, 'Invalid format specifier'
187
+ end
188
+ end
189
+ end
190
+
191
+ def process_layer
192
+ return unless @layer
193
+ unless %i(front back).include?(@layer.to_sym)
194
+ raise ArgumentError, 'Invalid Axis layer'
195
+ end
196
+ end
197
+
198
+ def process_properties
199
+ return unless @properties
200
+ valid_elements = %i(ticks major_ticks minor_ticks grid labels axis)
201
+ unless (@properties.keys - valid_elements).empty?
202
+ raise ArgumentError, 'Invalid property element'
203
+ end
204
+ end
205
+
206
+ end
207
+
208
+ end
@@ -0,0 +1,193 @@
1
+ module Plotrb
2
+
3
+ # Some internal methods for mixin
4
+ module Base
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # add setter methods to attributes
13
+ def add_attributes(*vars)
14
+ @attributes ||= []
15
+ @attributes.concat(vars)
16
+ vars.each do |var|
17
+ define_method("#{var}=") { |value|
18
+ instance_variable_set("@#{var}", value)
19
+ }
20
+ end
21
+ end
22
+
23
+ def attributes
24
+ @attributes
25
+ end
26
+
27
+ end
28
+
29
+ # @return [Array<Symbol>] attributes of the particular instance combined
30
+ # with attributes of the class
31
+ def attributes
32
+ singleton_attr = self.singleton_class.attributes || []
33
+ class_attr = self.class.attributes || []
34
+ singleton_attr.concat(class_attr).uniq
35
+ end
36
+
37
+ # add and set new attributes and values to the instance
38
+ # @param args [Hash] attributes in the form of a Hash
39
+ def set_attributes(args)
40
+ args.each do |k, v|
41
+ # use singleton_class as attributes are instance-specific
42
+ self.singleton_class.add_attributes(k)
43
+ self.instance_variable_set("@#{k}", v) unless v.nil?
44
+ end
45
+ end
46
+
47
+ # add new attributes to the instance
48
+ # @param args [Array<Symbol>] the attributes to add to the instance
49
+ def add_attributes(*args)
50
+ self.singleton_class.add_attributes(*args)
51
+ end
52
+
53
+ # @return [Array<Symbol>] attributes that have values
54
+ def defined_attributes
55
+ attributes.reject { |attr| self.instance_variable_get("@#{attr}").nil? }
56
+ end
57
+
58
+ # to be implemented in each Plotrb class
59
+ def attribute_post_processing
60
+ raise NotImplementedError
61
+ end
62
+
63
+ # @return [Hash] recursively construct a massive hash
64
+ def collect_attributes
65
+ attribute_post_processing
66
+ collected = {}
67
+ defined_attributes.each do |attr|
68
+ value = self.instance_variable_get("@#{attr}")
69
+ # change snake_case attributes to camelCase used in Vega's JSON spec
70
+ json_attr = classify(attr, :json)
71
+ if value.respond_to?(:collect_attributes)
72
+ collected[json_attr] = value.collect_attributes
73
+ elsif value.is_a?(Array)
74
+ collected[json_attr] = [].concat(value.collect{ |v|
75
+ v.respond_to?(:collect_attributes) ? v.collect_attributes : v
76
+ })
77
+ else
78
+ collected[json_attr] = value
79
+ end
80
+ end
81
+ collected
82
+ end
83
+
84
+ def define_boolean_attribute(method)
85
+ # when setting boolean values, eg. foo.bar sets bar attribute to true,
86
+ # foo.bar? returns state of bar attribute
87
+ define_singleton_method(method) do |&block|
88
+ self.instance_variable_set("@#{method}", true)
89
+ self.instance_eval(&block) if block
90
+ self
91
+ end
92
+ define_singleton_method("#{method}?") do
93
+ self.instance_variable_get("@#{method}")
94
+ end
95
+ end
96
+
97
+ def define_boolean_attributes(*methods)
98
+ methods.each { |m| define_boolean_attribute(m) }
99
+ end
100
+
101
+ def define_single_val_attribute(method, proc=nil)
102
+ # when only single value is allowed, eg. foo.bar(1)
103
+ # proc is passed in to process value before assigning to attributes
104
+ define_singleton_method(method) do |*args, &block|
105
+ case args.size
106
+ when 0
107
+ self.instance_variable_get("@#{method}")
108
+ when 1
109
+ val = proc.is_a?(Proc) ? proc.call(args[0]) : args[0]
110
+ self.instance_variable_set("@#{method}", val)
111
+ self.instance_eval(&block) if block
112
+ self
113
+ else
114
+ raise ArgumentError
115
+ end
116
+ end
117
+ end
118
+
119
+ def define_single_val_attributes(*methods)
120
+ methods.each { |m| define_single_val_attribute(m) }
121
+ end
122
+
123
+ def define_multi_val_attribute(method, proc=nil)
124
+ # when multiple values are allowed, eg. foo.bar(1,2) or foo.bar([1,2])
125
+ # proc is passed in to process values before assigning to attributes
126
+ define_singleton_method(method) do |*args, &block|
127
+ case args.size
128
+ when 0
129
+ self.instance_variable_get("@#{method}")
130
+ else
131
+ vals = proc.is_a?(Proc) ? proc.call(*args) : [args].flatten
132
+ self.instance_variable_set("@#{method}", vals)
133
+ self.instance_eval(&block) if block
134
+ self
135
+ end
136
+ end
137
+ end
138
+
139
+ def define_multi_val_attributes(*methods)
140
+ methods.each { |m| define_multi_val_attribute(m) }
141
+ end
142
+
143
+ def classify(name, format=nil)
144
+ klass = name.to_s.split('_').collect(&:capitalize).join
145
+ if format == :json
146
+ klass[0].downcase + klass[1..-1]
147
+ else
148
+ klass
149
+ end
150
+ end
151
+
152
+ # monkey patch Hash class to support reverse_merge and collect_attributes
153
+ class ::Hash
154
+
155
+ def attribute_post_processing
156
+ # nothing to do for Hash
157
+ end
158
+
159
+ def reverse_merge(other_hash)
160
+ other_hash.merge(self)
161
+ end
162
+
163
+ def collect_attributes
164
+ collected = {}
165
+ self.each do |k, v|
166
+ json_attr = classify(k, :json)
167
+ if v.respond_to?(:collect_attributes)
168
+ collected[json_attr] = v.collect_attributes
169
+ elsif v.is_a?(Array)
170
+ collected[json_attr] = [].concat(v.collect{ |va|
171
+ va.respond_to?(:collect_attributes) ? va.collect_attributes : va
172
+ })
173
+ else
174
+ collected[json_attr] = v
175
+ end
176
+ end
177
+ collected
178
+ end
179
+
180
+ def classify(name, format=nil)
181
+ klass = name.to_s.split('_').collect(&:capitalize).join
182
+ if format == :json
183
+ klass[0].downcase + klass[1..-1]
184
+ else
185
+ klass
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+
193
+ end