File: matrix.rb

package info (click to toggle)
ruby-rubyvis 0.6.1%2Bdfsg1-2
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 1,808 kB
  • ctags: 679
  • sloc: ruby: 11,114; makefile: 2
file content (188 lines) | stat: -rw-r--r-- 7,139 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
module Rubyvis
  class Layout
    # Alias for Rubyvis::Layout::Matrix
    def self.Matrix
      Rubyvis::Layout::Matrix
    end

    # Implements a network visualization using a matrix view. This is, in
    # effect, a visualization of the graph's <i>adjacency matrix</i>: the cell at
    # row <i>i</i>, column <i>j</i>, corresponds to the link from node <i>i</i> to
    # node <i>j</i>. The fill color of each cell is binary by default, and
    # corresponds to whether a link exists between the two nodes. If the underlying
    # graph has links with variable values, the <tt>fillStyle</tt> property can be
    # substited to use an appropriate color function, such as {@link pv.ramp}.
    #
    # <p>For undirected networks, the matrix is symmetric around the diagonal. For
    # directed networks, links in opposite directions can be rendered on opposite
    # sides of the diagonal using <tt>directed(true)</tt>. The graph is assumed to
    # be undirected by default.
    #
    # <p>The mark prototypes for this network layout are slightly different than
    # other implementations:<ul>
    #
    # <li><tt>node</tt> - unsupported; undefined. No mark is needed to visualize
    # nodes directly, as the nodes are implicit in the location (rows and columns)
    # of the links.
    #
    # <p><li><tt>link</tt> - for rendering links; typically a {@link pv.Bar}. The
    # link mark is added directly to the layout, with the data property defined as
    # all possible pairs of nodes. Each pair is represented as a
    # {@link pv.Network.Layout.Link}, though the <tt>linkValue</tt> attribute may
    # be 0 if no link exists in the graph.
    #
    # <p><li><tt>label</tt> - for rendering node labels; typically a
    # {@link pv.Label}. The label mark is added directly to the layout, with the
    # data property defined via the layout's <tt>nodes</tt> property; note,
    # however, that the nodes are duplicated so as to provide a label across the
    # top and down the side. Properties such as <tt>strokeStyle</tt> and
    # <tt>fillStyle</tt> can be overridden to compute properties from node data
    # dynamically.
    #
    # </ul>For more details on how to use this layout, see
    # {@link pv.Layout.Network}.
    class Matrix < Network
      @properties=Network.properties.dup
      attr_accessor :_n, :_dx, :_dy, :_labels, :_pairs
      def build_implied_post(s)
        @_n = s.nodes.size
        @_dx = s.width.to_f / @_n
        @_dy = s.height.to_f / @_n
        @_labels = s._matrix.labels
        @_pairs = s._matrix.pairs
      end
      # Deletes special add from network
      def _link # :nodoc:
        #that=self
        l=Mark.new().
        mark_extend(@node).
        data(lambda {|d| [d.source_node, d.target_node] }).
        fill_style(nil).
        line_width(lambda {|d,_p| _p.link_value * 1.5 }).
        stroke_style("rgba(0,0,0,.2)")
        l
      end

      def initialize
        super
        @_n=nil, # cached matrix size
        @_dx=nil, # cached cell width
        @_dy=nil, # cached cell height
        @_labels=nil, # cached labels (array of strings)
        @_pairs=nil, # cached pairs (array of links)
        that=self
        #/* Links are all pairs of nodes. */
        @link.data(lambda  {that._pairs}).
          left(lambda { that._dx * (self.index % that._n) }).
          top(lambda  { that._dy * (self.index / that._n.to_f).floor }).
          width(lambda  { that._dx }).
          height(lambda  { that._dy }).
          line_width(1.5).
          stroke_style("#fff").
        fill_style(lambda {|l| l.link_value!=0 ? "#555" : "#eee" })

        @link.parent = self
        #/* No special add for links! */

        # delete this.link.add;

        #/* Labels are duplicated for top & left. */
        @node_label.
        data(lambda  { that._labels }).
        left(lambda  { (self.index & 1)!=0 ? that._dx * ((self.index >> 1) + 0.5) : 0 }).
        top(lambda  { (self.index & 1)!=0 ? 0 : that._dy * ((self.index >> 1) + 0.5) }).
        text_margin(4).text_align(lambda  { (self.index & 1)!=0 ? "left" : "right"; }).
        text_angle(lambda  { (self.index & 1)!=0 ? -Math::PI / 2.0 : 0; });
        @node=nil
      end
      def self.defaults
        Matrix.new.mark_extend(Network.defaults).
        directed(true)
      end
      ##
      # :attr: directed
      #
      # Whether this matrix visualization is directed (bidirectional). By default,
      # the graph is assumed to be undirected, such that the visualization is
      # symmetric across the matrix diagonal. If the network is directed, then
      # forward links are drawn above the diagonal, while reverse links are drawn
      # below.
      #
      # @type boolean
      # @name pv.Layout.Matrix.prototype.directed
      #/

      attr_accessor_dsl :directed


      # Specifies an optional sort function. The sort function follows the same
      # comparator contract required by {@link pv.Dom.Node#sort}. Specifying a sort
      # function provides an alternative to sort the nodes as they are specified by
      # the <tt>nodes</tt> property; the main advantage of doing this is that the
      # comparator function can access implicit fields populated by the network
      # layout, such as the <tt>linkDegree</tt>.
      #
      # <p>Note that matrix visualizations are particularly sensitive to order. This
      # is referred to as the seriation problem, and many different techniques exist
      # to find good node orders that emphasize clusters, such as spectral layout and
      # simulated annealing.
      #
      # @param {function} f comparator function for nodes.
      # @returns {pv.Layout.Matrix} this.
      #/
      def sort(f=nil,&block)
        f||=block
        @sort=f
        self
      end
      def build_implied(s) # :nodoc:
        return nil if network_build_implied(s)
        nodes = s.nodes
        links = s.links
        sort = @sort

        n = nodes.size
        index = Rubyvis.range(n)
        labels = []
        pairs = []
        map = {}

        s._matrix = OpenStruct.new({:labels=> labels, :pairs=> pairs})

        #/* Sort the nodes. */
        if sort
          index.sort! {|a,b| sort.call(nodes[a],nodes[b])}
        end
        #/* Create pairs. */
        n.times {|i|
          n.times {|j|
            a = index[i]
            b = index[j]
            _p = OpenStruct.new({
              :row=> i,
              :col=> j,
              :source_node=> nodes[a],
              :target_node=> nodes[b],
            :link_value=> 0})
            map["#{a}.#{b}"] = _p
            pairs.push(map["#{a}.#{b}"])
          }
        }
        #/* Create labels. */
        n.times {|i|
          a = index[i]
          labels.push(nodes[a], nodes[a])
        }
        #/* Accumulate link values. */
        links.each_with_index {|l,i|
          source = l.source_node.index
          target = l.target_node.index
          value = l.link_value
          map["#{source}.#{target}"].link_value += value
          map["#{target}.#{source}"].link_value += value if (!s.directed)
        }
        build_implied_post(s)
      end
    end
  end
end