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