rubyvis 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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