rubyvis 0.2.2 → 0.3.0

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.
@@ -79,7 +79,7 @@ module Rubyvis
79
79
  attr_accessor :_x, :_y, :_values, :prop
80
80
 
81
81
  def self.defaults
82
- Stack.new.extend(Layout.defaults).orient("bottom-left").offset("zero").layers([[]])
82
+ Stack.new.mark_extend(Layout.defaults).orient("bottom-left").offset("zero").layers([[]])
83
83
  end
84
84
 
85
85
  # Constructs a new, empty stack layout. Layouts are not typically constructed
@@ -288,7 +288,7 @@ module Rubyvis
288
288
  end
289
289
  def add(type)
290
290
  that = @that
291
- that.add( Rubyvis.Panel ).data(lambda { that.layers() }).add(type).extend( self )
291
+ that.add( Rubyvis.Panel ).data(lambda { that.layers() }).add(type).mark_extend( self )
292
292
  end
293
293
  end
294
294
 
@@ -0,0 +1,357 @@
1
+ module Rubyvis
2
+ class Layout
3
+ def self.Treemap
4
+ Rubyvis::Layout::Treemap
5
+ end
6
+
7
+ # Implements a space-filling rectangular layout, with the hierarchy
8
+ # represented via containment. Treemaps represent nodes as boxes, with child
9
+ # nodes placed within parent boxes. The size of each box is proportional
10
+ # to the size of the node in the tree. This particular algorithm is taken from Bruls,
11
+ # D.M., C. Huizing, and J.J. van Wijk, <a
12
+ # href="http://www.win.tue.nl/~vanwijk/stm.pdf">"Squarified Treemaps"</a> in
13
+ # <i>Data Visualization 2000, Proceedings of the Joint Eurographics and IEEE
14
+ # TCVG Sumposium on Visualization</i>, 2000, pp. 33-42.
15
+ #
16
+ # <p>The meaning of the exported mark prototypes changes slightly in the
17
+ # space-filling implementation:<ul>
18
+ #
19
+ # <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Bar}. The node
20
+ # data is populated with <tt>dx</tt> and <tt>dy</tt> attributes, in addition to
21
+ # the standard <tt>x</tt> and <tt>y</tt> position attributes.
22
+ #
23
+ # <p><li><tt>leaf</tt> - for rendering leaf nodes only, with no fill or stroke
24
+ # style by default; typically a {@link pv.Panel} or another layout!
25
+ #
26
+ # <p><li><tt>link</tt> - unsupported; undefined. Links are encoded implicitly
27
+ # in the arrangement of the space-filling nodes.
28
+ #
29
+ # <p><li><tt>label</tt> - for rendering node labels; typically a
30
+ # {@link pv.Label}.
31
+ #
32
+ # </ul>For more details on how to use this layout, see
33
+ # {@link pv.Layout.Hierarchy}.
34
+ #
35
+ class Treemap < Hierarchy
36
+ @properties=Hierarchy.properties.dup
37
+ def initialize
38
+ super
39
+ @size=lambda {|d| d.node_value.to_f}
40
+ @node.stroke_style("#fff").
41
+ fill_style("rgba(31, 119, 180, .25)").
42
+ width(lambda {|n| n.dx}).
43
+ height(lambda {|n| n.dy })
44
+
45
+ @node_label.
46
+ visible(lambda {|n| !n.first_child }).
47
+ left(lambda {|n| n.x + (n.dx / 2.0) }).
48
+ top(lambda {|n| n.y + (n.dy / 2.0) }).
49
+ text_align("center").
50
+ text_angle(lambda {|n| n.dx > n.dy ? 0 : -Math::PI / 2.0 })
51
+
52
+ end
53
+
54
+ def leaf
55
+ m=Rubyvis::Mark.new.
56
+ mark_extend(self.node).
57
+ fill_style(nil).
58
+ stroke_style(nil).
59
+ visible(lambda {|n| !n.first_child })
60
+ m.parent = self
61
+ m
62
+ end
63
+ def link
64
+ nil
65
+ end
66
+
67
+ ##
68
+ # :attr: round
69
+ # Whether node sizes should be rounded to integer values. This has a similar
70
+ # effect to setting <tt>antialias(false)</tt> for node values, but allows the
71
+ # treemap algorithm to accumulate error related to pixel rounding.
72
+ #
73
+ # @type boolean
74
+
75
+
76
+
77
+ ##
78
+ # :attr: padding_left
79
+ # The left inset between parent add child in pixels. Defaults to 0.
80
+ #
81
+ # @type number
82
+ # @see #padding
83
+
84
+
85
+
86
+ ##
87
+ # :attr: padding_rigth
88
+ # The right inset between parent add child in pixels. Defaults to 0.
89
+ #
90
+ # @type number
91
+ # @name pv.Layout.Treemap.prototype.paddingRight
92
+ # @see #padding
93
+
94
+
95
+ ##
96
+ # :attr: padding_top
97
+ # The top inset between parent and child in pixels. Defaults to 0.
98
+ #
99
+ # @type number
100
+ # @name pv.Layout.Treemap.prototype.paddingTop
101
+ # @see #padding
102
+
103
+
104
+ ##
105
+ # :attr: padding_bottom
106
+ # The bottom inset between parent and child in pixels. Defaults to 0.
107
+ #
108
+ # @type number
109
+ # @name pv.Layout.Treemap.prototype.paddingBottom
110
+ # @see #padding
111
+
112
+
113
+ ##
114
+ # :attr: mode
115
+ # The treemap algorithm. The default value is "squarify". The "slice-and-dice"
116
+ # algorithm may also be used, which alternates between horizontal and vertical
117
+ # slices for different depths. In addition, the "slice" and "dice" algorithms
118
+ # may be specified explicitly to control whether horizontal or vertical slices
119
+ # are used, which may be useful for nested treemap layouts.
120
+ #
121
+ # @type string
122
+ # @name pv.Layout.Treemap.prototype.mode
123
+ # @see <a
124
+ # href="ftp://ftp.cs.umd.edu/pub/hcil/Reports-Abstracts-Bibliography/2001-06html/2001-06.pdf"
125
+ # >"Ordered Treemap Layouts"</a> by B. Shneiderman &amp; M. Wattenberg, IEEE
126
+ # InfoVis 2001.
127
+
128
+
129
+ ##
130
+ # :attr: order
131
+ # The sibling node order. A <tt>null</tt> value means to use the sibling order
132
+ # specified by the nodes property as-is; "reverse" will reverse the given
133
+ # order. The default value "ascending" will sort siblings in ascending order of
134
+ # size, while "descending" will do the reverse. For sorting based on data
135
+ # attributes other than size, use the default <tt>null</tt> for the order
136
+ # property, and sort the nodes beforehand using the {@link pv.Dom} operator.
137
+ #
138
+ # @type string
139
+ # @name pv.Layout.Treemap.prototype.order
140
+
141
+
142
+
143
+
144
+ attr_accessor_dsl :round, :padding_left, :padding_right, :padding_top, :padding_bottom, :mode, :order
145
+
146
+ # Default propertiess for treemap layouts. The default mode is "squarify" and the default order is "ascending".
147
+ def self.defaults
148
+ Rubyvis::Layout::Treemap.new.mark_extend(Rubyvis::Layout::Hierarchy.defaults).
149
+ mode("squarify"). # squarify, slice-and-dice, slice, dice
150
+ order('ascending') # ascending, descending, reverse, nil
151
+ end
152
+
153
+ # Alias for setting the left, right, top and bottom padding properties
154
+ # simultaneously.
155
+ def padding(n)
156
+ padding_left(n).padding_right(n).padding_top(n).padding_bottom(n)
157
+ end
158
+ def _size(d)
159
+ @size.call(d)
160
+ end
161
+
162
+ ##
163
+ # Specifies the sizing function. By default, the size function uses the
164
+ # +node_value+ attribute of nodes as a numeric value:
165
+ # <p>The sizing function is invoked for each leaf node in the tree, per the
166
+ # <tt>nodes</tt> property. For example, if the tree data structure represents a
167
+ # file system, with files as leaf nodes, and each file has a <tt>bytes</tt>
168
+ # attribute, you can specify a size function as:
169
+ #
170
+ # <pre> .size(function(d) d.bytes)</pre>
171
+ #
172
+ # @param {function} f the new sizing function.
173
+ # @returns {pv.Layout.Treemap} this.
174
+
175
+ def size(f)
176
+ @size=Rubyvis.functor(f)
177
+ end
178
+
179
+
180
+ def build_implied(s)
181
+ return nil if hierarchy_build_implied(s)
182
+
183
+ that=self
184
+ nodes = s.nodes
185
+ root = nodes[0]
186
+ stack = Mark.stack
187
+
188
+ left = s.padding_left
189
+ right = s.padding_right
190
+ top = s.padding_top
191
+ bottom = s.padding_bottom
192
+ left||=0
193
+ right||=0
194
+ top||=0
195
+ bottom||=0
196
+ size=lambda {|n| n.size}
197
+ round = s.round ?
198
+ lambda {|a| a.round } :
199
+ lambda {|a| a.to_f}
200
+ mode = s.mode
201
+
202
+ slice=lambda { |row, sum, horizontal, x, y, w, h|
203
+ # puts "slice:#{sum},#{horizontal},#{x},#{y},#{w},#{h}"
204
+ d=0
205
+ row.size.times {|i|
206
+ n=row[i]
207
+ # puts "i:#{i},d:#{d}"
208
+ if horizontal
209
+ n.x = x + d
210
+ n.y = y
211
+ d += n.dx = round.call(w * n.size / sum.to_f)
212
+ n.dy = h
213
+ else
214
+ n.x = x
215
+ n.y = y + d
216
+ n.dx = w
217
+ d += n.dy = round.call(h * n.size / sum.to_f)
218
+ end
219
+ # puts "n.x:#{n.x}, n.y:#{n.y}, n.dx:#{n.dx}, n.dy:#{n.dy}"
220
+ }
221
+
222
+
223
+ if (row.last) # correct on-axis rounding error
224
+ n=row.last
225
+ if (horizontal)
226
+ n.dx += w - d
227
+ else
228
+ n.dy += h - d
229
+ end
230
+ end
231
+ }
232
+
233
+ ratio=lambda {|row, l|
234
+ rmax = -Infinity
235
+ rmin = Infinity
236
+ s = 0
237
+ row.each_with_index {|v,i|
238
+ r = v.size
239
+ rmin = r if (r < rmin)
240
+ rmax = r if (r > rmax)
241
+ s += r
242
+ }
243
+ s = s * s
244
+ l = l * l
245
+ [l * rmax / s.to_f, s.to_f / (l * rmin)].max
246
+ }
247
+
248
+ layout=lambda {|n,i|
249
+ x = n.x + left
250
+ y = n.y + top
251
+ w = n.dx - left - right
252
+ h = n.dy - top - bottom
253
+
254
+ # puts "Layout: '#{n.node_name}', #{n.x}, #{n.y}, #{n.dx}, #{n.dy}"
255
+ #/* Assume squarify by default. */
256
+ if (mode != "squarify")
257
+ slice.call(n.child_nodes, n.size, ( mode == "slice" ? true : mode == "dice" ? false : (i & 1)!=0), x, y, w, h)
258
+ else
259
+ row = []
260
+ mink = Infinity
261
+ l = [w,h].min
262
+ k = w * h / n.size.to_f
263
+ #/* Abort if the size is nonpositive. */
264
+
265
+ if (n.size > 0)
266
+ #/* Scale the sizes to fill the current subregion. */
267
+ n.visit_before {|n,i| n.size *= k }
268
+
269
+ #/** @private Position the specified nodes along one dimension. */
270
+ position=lambda {|row|
271
+ horizontal = w == l
272
+ sum = Rubyvis.sum(row, size)
273
+ r = l>0 ? round.call(sum / l.to_f) : 0
274
+ slice.call(row, sum, horizontal, x, y, horizontal ? w : r, horizontal ? r : h)
275
+ if horizontal
276
+ y += r
277
+ h -= r
278
+ else
279
+ x += r
280
+ w -= r
281
+ end
282
+ l = [w, h].min
283
+ horizontal
284
+ }
285
+
286
+ children = n.child_nodes.dup # copy
287
+ while (children.size>0) do
288
+ child = children[children.size - 1]
289
+ if (child.size==0)
290
+ children.pop
291
+ next
292
+ end
293
+ row.push(child)
294
+
295
+ k = ratio.call(row, l)
296
+
297
+ if (k <= mink)
298
+ children.pop
299
+ mink = k
300
+ else
301
+ row.pop
302
+ position.call(row)
303
+ row.clear
304
+ mink = Infinity
305
+ end
306
+ end
307
+
308
+ #/* correct off-axis rounding error */
309
+
310
+ if (position.call(row))
311
+ row.each {|v|
312
+ v.dy+=h
313
+ }
314
+ else
315
+ row.each {|v|
316
+ v.dx+=w
317
+ }
318
+ end
319
+ end
320
+ end
321
+ }
322
+
323
+
324
+ stack.unshift(nil)
325
+ root.visit_after {|nn,i|
326
+ nn.depth = i
327
+ nn.x = nn.y = nn.dx = nn.dy = 0
328
+ if nn.first_child
329
+ nn.size=Rubyvis.sum(nn.child_nodes, lambda {|v| v.size})
330
+ else
331
+ stack[0]=nn
332
+ nn.size=that._size(stack[0])
333
+ end
334
+ }
335
+ stack.shift()
336
+
337
+ #/* Sort. */
338
+
339
+ case s.order
340
+ when 'ascending'
341
+ root.sort(lambda {|a,b| a.size<=>b.size})
342
+ when 'descending'
343
+ root.sort(lambda {|a,b| b.size<=>a.size})
344
+ when 'reverse'
345
+ root.reverse
346
+ end
347
+ # /* Recursively compute the layout. */
348
+ root.x = 0;
349
+ root.y = 0;
350
+ root.dx = s.width
351
+ root.dy = s.height
352
+ root.visit_before {|n,i| layout.call(n,i)}
353
+ end
354
+
355
+ end
356
+ end
357
+ end
@@ -1,4 +1,3 @@
1
-
2
1
  module Rubyvis
3
2
  # Constructs a new mark with default properties. Marks, with the exception of
4
3
  # the root panel, are not typically constructed directly; instead, they are
@@ -488,7 +487,7 @@ module Rubyvis
488
487
  # type of mark as this mark. (Note that for inheritance to be useful,
489
488
  # properties with the same name on different mark types should
490
489
  # have equivalent meaning.)
491
- def extend(proto)
490
+ def mark_extend(proto)
492
491
  @proto=proto
493
492
  @target=proto.target
494
493
  self
@@ -572,9 +571,9 @@ module Rubyvis
572
571
  # * @param {function} type the type of mark to add; a constructor, such as
573
572
  # +Rubyvis::Bar+
574
573
  # * @returns {Mark} the new mark.
575
- # * @see #extend
574
+ # * @see #mark_extend
576
575
  def add(type)
577
- parent.add(type).extend(self)
576
+ parent.add(type).mark_extend(self)
578
577
  end
579
578
  # Returns an anchor with the specified name. All marks support the five
580
579
  # standard anchor names:
@@ -930,7 +929,7 @@ module Rubyvis
930
929
  mark.index=nil
931
930
  end while(mark=mark.parent)
932
931
  end
933
- def context(scene,index,f) # :nodoc:
932
+ def context(scene, index, f) # :nodoc:
934
933
  proto=Mark
935
934
  stack=Mark.stack
936
935
  oscene=Mark.scene
@@ -1036,14 +1035,17 @@ module Rubyvis
1036
1035
  end
1037
1036
  end
1038
1037
 
1039
-
1040
-
1041
1038
  # Evaluates the specified array of properties for the specified
1042
1039
  # instance <tt>s</tt> in the scene graph.
1043
1040
  #
1044
1041
  # @param s a node in the scene graph; the instance of the mark to build.
1045
1042
  # @param properties an array of properties.
1046
- def build_properties(ss, props) # :nodoc:
1043
+
1044
+ def build_properties(ss,props) # :nodoc:
1045
+ mark_build_properties(ss,props)
1046
+ end
1047
+
1048
+ def mark_build_properties(ss, props) # :nodoc:
1047
1049
  #p props
1048
1050
  props.each do |prop|
1049
1051
  v=prop.value
@@ -75,7 +75,7 @@ module Rubyvis
75
75
  # @returns {Rubyvis::Anchor} this anchor.
76
76
  # @see Rubyvis.Mark#add
77
77
 
78
- def extend(proto)
78
+ def mark_extend(proto)
79
79
  @proto=proto
80
80
  self
81
81
  end
@@ -211,7 +211,7 @@ module Rubyvis
211
211
  end
212
212
  def self.defaults
213
213
  a= Rubyvis::Colors.category20
214
- Area.new.extend(Mark.defaults).line_width(1.5).fill_style(lambda {a.scale(self.parent.index)}).interpolate('linear').tension(0.7)
214
+ Area.new.mark_extend(Mark.defaults).line_width(1.5).fill_style(lambda {a.scale(self.parent.index)}).interpolate('linear').tension(0.7)
215
215
  end
216
216
  def anchor(name)
217
217
  area_anchor(name)
@@ -61,7 +61,7 @@ module Rubyvis
61
61
  # style is a categorical color.
62
62
  def self.defaults
63
63
  a=Rubyvis.Colors.category20()
64
- Bar.new.extend(Mark.defaults).line_width(1.5).fill_style( lambda {
64
+ Bar.new.mark_extend(Mark.defaults).line_width(1.5).fill_style( lambda {
65
65
  a.scale(self.parent.index)
66
66
  })
67
67
  end