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.
@@ -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