rubyvis 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,6 +4,9 @@ module Rubyvis
4
4
  end
5
5
  class Layout < Rubyvis::Panel
6
6
  @properties=Panel.properties.dup
7
+ def build_properties(s,properties)
8
+ layout_build_properties(s,properties)
9
+ end
7
10
  def layout_build_properties(s,properties)
8
11
  mark_build_properties(s,properties)
9
12
  end
@@ -29,3 +32,8 @@ module Rubyvis
29
32
  end
30
33
 
31
34
  require 'rubyvis/layout/stack'
35
+ require 'rubyvis/layout/network'
36
+ require 'rubyvis/layout/hierarchy'
37
+ require 'rubyvis/layout/treemap'
38
+
39
+
@@ -0,0 +1,247 @@
1
+ module Rubyvis
2
+ class Layout
3
+ # Alias for Rubyvis::Layout::Hierarchy
4
+ def self.Hierarchy
5
+ Rubyvis::Layout::Hierarchy
6
+ end
7
+ # Represents an abstract layout for hierarchy diagrams. This class is a
8
+ # specialization of Rubyvis::Layout::Network, providing the basic structure
9
+ # for both hierarchical node-link diagrams (such as Reingold-Tilford trees) and
10
+ # space-filling hierarchy diagrams (such as sunbursts and treemaps).
11
+ #
12
+ # <p>Unlike general network layouts, the +links+ property need not be
13
+ # defined explicitly. Instead, the links are computed implicitly from the
14
+ # +parent_node+ attribute of the node objects, as defined by the
15
+ # +nodes+ property. This implementation is also available as
16
+ # +Hierarchy.links, for reuse with non-hierarchical layouts; for example, to
17
+ # render a tree using force-directed layout.
18
+ #
19
+ # <p>Correspondingly, the +nodes+ property is represented as a union of
20
+ # Rubyvis::Layout::Network::Node} and Rubyvis::Dom::Node. To construct a node
21
+ # hierarchy from a simple JSON map, use the Rubyvis::Dom operator; this
22
+ # operator also provides an easy way to sort nodes before passing them to the
23
+ # layout.
24
+ #
25
+ # <p>For more details on how to use this layout, see
26
+ # Rubyvis::Layout::Network
27
+ #
28
+ # @see Rubyvis::Layout::Cluster
29
+ # @see Rubyvis::Layout::Partition
30
+ # @see Rubyvis::Layout::Tree
31
+ # @see Rubyvis::Layout::Treemap
32
+ # @see Rubyvis::Layout::Indent
33
+ # @see Rubyvis::Layout::Pack
34
+ class Hierarchy < Network
35
+ @properties=Network.properties.dup
36
+ def initialize
37
+ super
38
+ @link.stroke_style("#ccc")
39
+ end
40
+ def build_implied(s)
41
+ hierarchy_build_implied(s)
42
+ end
43
+ # @private Compute the implied links. (Links are null by default.) */
44
+ def hierarchy_build_implied(s)
45
+ s.links=self.links() if !s.links
46
+ network_build_implied(s)
47
+ end
48
+
49
+ # The implied links; computes links using the <tt>parent_node</tt> attribute.
50
+ def links
51
+ l=self.nodes().find_all {|n| n.parent_node}
52
+ l.map {|n|
53
+ OpenStruct.new({
54
+ :source_node=>n,
55
+ :target_node=>n.parent_node,
56
+ :link_value=>1
57
+ })}
58
+ end
59
+ def node_link
60
+ NodeLink.new(self)
61
+ end
62
+ def fill
63
+ Fill.new(self)
64
+ end
65
+ end
66
+
67
+ class NodeLink
68
+ attr_accessor :ir, :_or, :orient, :w, :h
69
+ def initialize(obj)
70
+ @obj=obj
71
+ end
72
+ def build_implied(s)
73
+ nodes = s.nodes
74
+ orient= s.orient
75
+ orient=~/^(top|bottom)$/
76
+
77
+ horizontal = $1
78
+ w = s.width
79
+ h = s.height
80
+ # /* Compute default inner and outer radius. */
81
+ if (orient == "radial")
82
+ ir = s.inner_radius
83
+ _or = s.outer_radius
84
+ ir||=0
85
+ _or||=[w,h].min / 2.0
86
+ end
87
+ nodes.length.times {|i|
88
+ n = nodes[i]
89
+ n.mid_angle = (orient == "radial") ? mid_angle(n) : (horizontal ? Math::PI / 2.0 : 0)
90
+ n.x = x(n)
91
+ n.y = y(n)
92
+ n.mid_angle+=Math::PI if (n.first_child)
93
+ }
94
+ end
95
+ def radius(n)
96
+ n.parent_node ? (n.depth * (_or-ir)+ir) : 0
97
+ end
98
+ def min_angle(n)
99
+ n.parent_node ? ((n.breadth - 0.25) * 2 * Math::PI ) : 0
100
+ end
101
+ def x(n)
102
+ case orient
103
+ when "left"
104
+ n.depth*w
105
+ when "right"
106
+ w-n.depth*w
107
+ when "top"
108
+ n.breadth*w
109
+ when "bottom"
110
+ w-n.breath*w
111
+ when "radial"
112
+ w/2.0+radius(n)*Math.cos(n.mid_angle)
113
+ end
114
+ end
115
+ def y(n)
116
+ case orient
117
+ when "left"
118
+ n.breadth*h
119
+ when "right"
120
+ h-n.depth*h
121
+ when "top"
122
+ n.depth*h
123
+ when "bottom"
124
+ h-n.breath*h
125
+ when "radial"
126
+ h / 2.0 + radius(n) * Math.sin(n.mid_angle)
127
+ end # end case
128
+ end # end method
129
+ end # end class
130
+
131
+ class Fill
132
+ attr_accessor :ir, :_or, :orient, :w, :h
133
+ def initialize(obj)
134
+ @obj=obj
135
+ end
136
+ def constructor
137
+ self.node.
138
+ stroke_style("#fff").
139
+ fill_style("#ccc").
140
+ width(lambda {|n| n.dx}).
141
+ height(lambda {|n| n.dy}).
142
+ inner_radius(lambda {|n| n.inner_radius}).
143
+ outer_radius(lambda {|n| n.outer_radius}).
144
+ start_angle(lambda {|n| n.start_angle}).
145
+ angle(lambda {return n.angle})
146
+ self.node_label.
147
+ text_align("center").
148
+ left(lambda {|n| n.x+ (n.dx / 2.0) })
149
+ top(lambda {|n| n.y+(n.dy / 0.2)})
150
+ # delete this.link
151
+ end
152
+ def build_implied(s)
153
+ nodes = s.nodes
154
+ orient= s.orient
155
+ orient=~/^(top|bottom)$/
156
+ horizontal = $1
157
+ w = s.width
158
+ h = s.height
159
+ depth = -nodes[0].min_depth
160
+ if (orient == "radial")
161
+ ir = s.inner_radius
162
+ _or = s.outer_radius
163
+ ir||=0
164
+ depth = depth * 2 if ir!=0
165
+ _or||=[w,h].min / 2.0
166
+ end
167
+ nodes.each_with_index {|n,i|
168
+ n.x = x(n)
169
+ n.y = y(n)
170
+ if (orient == "radial")
171
+ n.inner_radius = inner_radius(n);
172
+ n.outer_radius = outer_radius(n);
173
+ n.start_angle = start_angle(n);
174
+ n.angle = angle(n);
175
+ n.mid_Angle = n.start_angle + n.angle / 2.0;
176
+ else
177
+ n.mid_angle = horizontal ? -Math::PI / 2.0 : 0;
178
+ end
179
+ n.dx = dx(n)
180
+ n.dy = dy(n)
181
+ }
182
+ end
183
+
184
+ def scale(d, depth)
185
+ (d+depth) / (1.0+depth)
186
+ end
187
+ def x(n)
188
+ case orient
189
+ when "left"
190
+ scale(n.min_depth,depth)*w
191
+ when "right"
192
+ (1-scale(n.max_depth,depth))*w
193
+ when "top"
194
+ n.min_breadth*w
195
+ when "bottom"
196
+ (1-n.max_breath)*w
197
+ when "radial"
198
+ w / 2.0
199
+ end
200
+ end
201
+ def y(n)
202
+ case orient
203
+ when "left"
204
+ n.min_breadth*h
205
+ when "right"
206
+ (1-n.max_breadth)*h
207
+ when "top"
208
+ scale(n.min_depth, depth) * h
209
+ when "bottom"
210
+ (1-scale(n.max_depth, depth)) * h
211
+ when "radial"
212
+ h / 2.0
213
+ end # end case
214
+ end # end method
215
+ def dx(n)
216
+ if orient=='left' or orient=='right'
217
+ (n.max_depth - n.min_depth) / (1.0 + depth) * w;
218
+ elsif orient=='top' or orient=='bottom'
219
+ (n.max_breadth - n.min_breadth) * w
220
+ elsif orient=='radial'
221
+ n.parent_node ? (n.inner_radius + n.outer_radius) * Math.cos(n.mid_angle) : 0
222
+ end
223
+ end
224
+ def dy(n)
225
+ if orient=='left' or orient=='right'
226
+ (n.max_breadth - n.min_breadth) * h;
227
+ elsif orient=='top' or orient=='bottom'
228
+ (n.max_depth - n.min_depth) / (1.0 + depth) * h;
229
+ elsif orient=='radial'
230
+ n.parent_node ? (n.inner_radius + n.outer_radius) * Math.sin(n.mid_angle) : 0
231
+ end
232
+ end
233
+ def inner_radius(n)
234
+ [0, scale(n.min_depth, depth/2.0)].max * (_or - _ir) + ir
235
+ end
236
+ def outer_radius(n)
237
+ scale(n.max_depth, depth / 2.0) * (_or - ir) + ir
238
+ end
239
+ def start_angle(n)
240
+ (n.parent_node ? n.min_breadth - 0.25 : 0) * 2 * Math::PI
241
+ end
242
+ def angle(n)
243
+ (n.parent_node ? n.max_breadt - n.min_breadth : 1 ) * 2 * Math::PI
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,228 @@
1
+ module Rubyvis
2
+ class Layout
3
+ # Alias for Rubyvis::Layout::Network
4
+ def self.Network
5
+ Rubyvis::Layout::Network
6
+ end
7
+
8
+ # Represents an abstract layout for network diagrams. This class
9
+ # provides the basic structure for both node-link diagrams (such as
10
+ # force-directed graph layout) and space-filling network diagrams (such as
11
+ # sunbursts and treemaps). Note that "network" here is a general term that
12
+ # includes hierarchical structures; a tree is represented using links from
13
+ # child to parent.
14
+ #
15
+ # <p>Network layouts require the graph data structure to be defined using two
16
+ # properties:<ul>
17
+ #
18
+ # <li><tt>nodes</tt> - an array of objects representing nodes. Objects in this
19
+ # array must conform to the {@link pv.Layout.Network.Node} interface; which is
20
+ # to say, be careful to avoid naming collisions with automatic attributes such
21
+ # as <tt>index</tt> and <tt>linkDegree</tt>. If the nodes property is defined
22
+ # as an array of primitives, such as numbers or strings, these primitives are
23
+ # automatically wrapped in an object; the resulting object's <tt>nodeValue</tt>
24
+ # attribute points to the original primitive value.
25
+ #
26
+ # <p><li><tt>links</tt> - an array of objects representing links. Objects in
27
+ # this array must conform to the {@link pv.Layout.Network.Link} interface; at a
28
+ # minimum, either <tt>source</tt> and <tt>target</tt> indexes or
29
+ # <tt>sourceNode</tt> and <tt>targetNode</tt> references must be set. Note that
30
+ # if the links property is defined after the nodes property, the links can be
31
+ # defined in terms of <tt>this.nodes()</tt>.
32
+ #
33
+ # </ul>
34
+ #
35
+ # <p>Three standard mark prototypes are provided:<ul>
36
+ #
37
+ # <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Dot}. The node
38
+ # mark is added directly to the layout, with the data property defined via the
39
+ # layout's <tt>nodes</tt> property. Properties such as <tt>strokeStyle</tt> and
40
+ # <tt>fillStyle</tt> can be overridden to compute properties from node data
41
+ # dynamically.
42
+ #
43
+ # <p><li><tt>link</tt> - for rendering links; typically a {@link pv.Line}. The
44
+ # link mark is added to a child panel, whose data property is defined as
45
+ # layout's <tt>links</tt> property. The link's data property is then a
46
+ # two-element array of the source node and target node. Thus, poperties such as
47
+ # <tt>strokeStyle</tt> and <tt>fillStyle</tt> can be overridden to compute
48
+ # properties from either the node data (the first argument) or the link data
49
+ # (the second argument; the parent panel data) dynamically.
50
+ #
51
+ # <p><li><tt>label</tt> - for rendering node labels; typically a
52
+ # {@link pv.Label}. The label mark is added directly to the layout, with the
53
+ # data property defined via the layout's <tt>nodes</tt> property. Properties
54
+ # such as <tt>strokeStyle</tt> and <tt>fillStyle</tt> can be overridden to
55
+ # compute properties from node data dynamically.
56
+ #
57
+ # </ul>Note that some network implementations may not support all three
58
+ # standard mark prototypes; for example, space-filling hierarchical layouts
59
+ # typically do not use a <tt>link</tt> prototype, as the parent-child links are
60
+ # implied by the structure of the space-filling <tt>node</tt> marks. Check the
61
+ # specific network layout for implementation details.
62
+ #
63
+ # <p>Network layout properties, including <tt>nodes</tt> and <tt>links</tt>,
64
+ # are typically cached rather than re-evaluated with every call to render. This
65
+ # is a performance optimization, as network layout algorithms can be
66
+ # expensive. If the network structure changes, call {@link #reset} to clear the
67
+ # cache before rendering. Note that although the network layout properties are
68
+ # cached, child mark properties, such as the marks used to render the nodes and
69
+ # links, <i>are not</i>. Therefore, non-structural changes to the network
70
+ # layout, such as changing the color of a mark on mouseover, do not need to
71
+ # reset the layout.
72
+ #
73
+ # @see Rubyvis::Layout::Hierarchy
74
+ # @see Rubyvis::Layout::Force
75
+ # @see Rubyvis::Layout::Matrix
76
+ # @see Rubyvis::Layout::Arc
77
+ # @see Rubyvis::Layout::Rollup
78
+ class Network < Rubyvis::Layout
79
+ @properties=Layout.properties.dup
80
+ attr_accessor :node, :link, :node_label
81
+ attr_accessor :_id
82
+ def initialize
83
+ super
84
+ @_id=Rubyvis.id()
85
+ @node=_node
86
+ @link=_link
87
+ @node_label=_node_label
88
+ end
89
+ # The node prototype. This prototype is intended to be used with a
90
+ # Dot mark in conjunction with the link prototype.
91
+ def _node
92
+ that=self
93
+ m=Mark.new().
94
+ data(lambda {that.nodes}).
95
+ stroke_style("#1f77b4").
96
+ fill_style("#fff").
97
+ left(lambda {|n| n.x }).
98
+ top(lambda {|n| n.y })
99
+ m.parent = self
100
+ m
101
+ end
102
+ module LinkAdd
103
+ attr_accessor :that
104
+ def add(type)
105
+ that=@that
106
+ return that.add(Rubyvis::Panel).
107
+ data(lambda {that.links}).
108
+ add(type).
109
+ mark_extend(self)
110
+ end
111
+ end
112
+
113
+ # The link prototype, which renders edges between source nodes and target
114
+ # nodes. This prototype is intended to be used with a Line mark in
115
+ # conjunction with the node prototype.
116
+
117
+ def _link
118
+ that=self
119
+ l=Mark.new().
120
+ mark_extend(@node).
121
+ data(lambda {|_p| [_p.source_node, p.target_node] }).
122
+ fill_style(nil).
123
+ line_width(lambda {|d,_p| p.link_value * 1.5 }).
124
+ stroke_style("rgba(0,0,0,.2)")
125
+ l.extend LinkAdd
126
+ l.that=self
127
+ l
128
+ end
129
+
130
+ # The node label prototype, which renders the node name adjacent to the node.
131
+ # This prototype is provided as an alternative to using the anchor on the
132
+ # node mark; it is primarily intended to be used with radial node-link
133
+ # layouts, since it provides a convenient mechanism to set the text angle.
134
+ #
135
+ # NOTE FOR PROTOVIS USERS: The original name of method was +label+
136
+ # but it was replaced to not conflict with Mark.label()
137
+ def _node_label
138
+ that=self
139
+ nl=Mark.new.mark_extend(@node).
140
+ text_margin(7).
141
+ text_baseline("middle").
142
+ text(lambda {|n| n.node_name ? n.node_name : n.node_value }).
143
+ text_angle(lambda {|n|
144
+ a = n.mid_angle
145
+ Rubyvis::Wedge.upright(a) ? a : (a + Math::PI)
146
+ }).
147
+ text_align(lambda {|n|
148
+ Rubyvis::Wedge.upright(n.mid_angle) ? "left" : "right"
149
+ })
150
+ nl.parent = self
151
+ nl
152
+ end
153
+ ##
154
+ # :class: Node
155
+ # Represents a node in a network layout. There is no explicit
156
+ # constructor; this class merely serves to document the attributes that are
157
+ # used on nodes in network layouts. (Note that hierarchical nodes place
158
+ # additional requirements on node representation, vis Rubyvis::Dom::Node
159
+
160
+ attr_accessor_dsl [:nodes, lambda {|v|
161
+ out=[]
162
+ v.each_with_index {|d,i|
163
+ d=OpenStruct.new({:node_value=>d}) unless d.respond_to? :node_value
164
+ d.index=i
165
+ out.push(d)
166
+ }
167
+ out
168
+ }]
169
+ attr_accessor_dsl [:links, lambda {|v|
170
+ out=[]
171
+ v.map {|d|
172
+ if !d.link_value.is_a? Numeric
173
+ d.link_value = !d.value.is_a?(Numeric) ? 1 : d.value
174
+ end
175
+ d
176
+ }
177
+ }]
178
+
179
+ # Resets the cache, such that changes to layout property definitions will be
180
+ # visible on subsequent render. Unlike normal marks (and normal layouts),
181
+ # properties associated with network layouts are not automatically re-evaluated
182
+ # on render; the properties are cached, and any expensive layout algorithms are
183
+ # only run after the layout is explicitly reset.
184
+ #
185
+ # (@returns Rubyvis::Layout::Network) self
186
+ def reset
187
+ self._id=Rubyvis.id()
188
+ self
189
+ end
190
+
191
+ # @private Skip evaluating properties if cached. */
192
+
193
+ def build_properties(s, properties)
194
+ s_id=s._id
195
+ s_id||=0
196
+ if (s_id < self._id)
197
+ layout_build_properties(s,properties)
198
+ end
199
+ end
200
+
201
+ def build_implied(s)
202
+ network_build_implied(s)
203
+ end
204
+ def network_build_implied(s)
205
+ layout_build_implied(s)
206
+ return true if (!s._id.nil? and s._id >= self._id)
207
+ s._id= self._id
208
+ s.nodes.each do |d|
209
+ d.link_degree=0
210
+ end
211
+
212
+ s.links.each do |d|
213
+ v=d.link_value
214
+ if !d.source_node
215
+ d.source_node=s.nodes[d.source]
216
+ end
217
+ d.source_node.link_degree+=v
218
+ if !d.target_node
219
+ d.target_node=s.nodes[d.target]
220
+ end
221
+ d.target_node.link_degree+=v
222
+
223
+ end
224
+ false
225
+ end
226
+ end
227
+ end
228
+ end