rubyvis 0.3.4 → 0.3.5

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.
data/lib/rubyvis.rb CHANGED
@@ -30,7 +30,7 @@ require 'rubyvis/mark/shorcut_methods'
30
30
  module Rubyvis
31
31
  @document=nil
32
32
  # Rubyvis version
33
- VERSION = '0.3.4'
33
+ VERSION = '0.3.5'
34
34
  # Protovis API on which current Rubyvis is based
35
35
  PROTOVIS_API_VERSION='3.3'
36
36
  # You actually can do it! http://snipplr.com/view/2137/uses-for-infinity-in-ruby/
@@ -27,24 +27,24 @@ module Rubyvis
27
27
  def self.color(format)
28
28
  return format.rgb if format.respond_to? :rgb
29
29
  if (format =~/([a-z]+)\((.*)\)/)
30
- m2 = $2.split(",")
30
+ color_type,color_data=$1,$2
31
+ m2 = color_data.split(",")
31
32
  a = 1
32
- if ['hsla','rgba'].include? $1
33
+ if ['hsla','rgba'].include? color_type
33
34
  a = m2[3].to_f
34
35
  return Color.transparent if (a==0)
35
36
  end
36
37
 
37
- if ['hsla','hsl'].include? $1
38
- h=m2[0].to_f
39
- s=m2[0].to_f.quo(100)
40
- l=m2[0].to_f.quo(100)
41
- return Color::Hsl.new(h,s,l,a).rgb()
38
+ if ['hsla','hsl'].include? color_type
39
+ h=m2[0].to_i
40
+ s=m2[1].to_f / 100
41
+ l=m2[2].to_f / 100
42
+ return Color::Hsl.new(h,s,l,a).rgb
42
43
  end
43
-
44
- if ['rgba','rgb'].include? $1
44
+
45
+ if ['rgba','rgb'].include? color_type
45
46
  parse=lambda {|c|
46
- f=c.to_f
47
- return (c[c.size-1]=='%') ? (f*2.55).round : f
47
+ (c[c.size-1,1]=='%') ? (c.to_f*2.55).round : c.to_i
48
48
  }
49
49
  r=parse.call(m2[0])
50
50
  g=parse.call(m2[1])
@@ -69,7 +69,7 @@ module Rubyvis
69
69
  g = format[3,2]
70
70
  b = format[5,2]
71
71
  end
72
- return Rubyvis.rgb(r.to_i(16), g.to_i(16), b.to_i(16), 1);
72
+ return Rubyvis.rgb(r.to_i(16), g.to_i(16), b.to_i(16), 1)
73
73
  end
74
74
 
75
75
  # Otherwise, pass-through unsupported colors. */
@@ -292,14 +292,10 @@ module Rubyvis
292
292
  @g=g
293
293
  @a=a
294
294
  @opacity=a
295
- if @a>0
296
- @color="rgb(#{r.to_i},#{g.to_i},#{b.to_i})"
297
- else
298
- @color="none"
299
- end
295
+ @color= @a > 0 ? "rgb(#{r.to_i},#{g.to_i},#{b.to_i})" : "none"
300
296
  end
301
297
  def ==(v)
302
- self.class==v.class and @r==v.r and @b==v.b and @g=v.g and @a=v.a
298
+ self.class==v.class and @r==v.r and @b==v.b and @g==v.g and @a==v.a
303
299
  end
304
300
 
305
301
  # Constructs a new RGB color with the same green, blue and alpha channels
@@ -333,20 +329,20 @@ module Rubyvis
333
329
  # darker are inverse operations, the results of a series of invocations of
334
330
  # these two methods might be inconsistent because of rounding errors.
335
331
  def lighter(k=1)
336
- k = 0.7**k
337
- i = 30
338
- r=self.r
339
- g=self.g
340
- b=self.b
341
- return Rubyvis.rgb(i, i, i, a) if (!r and !g and !b)
342
- r = i if (r and (r < i))
343
- g = i if (g and (g < i))
344
- b = i if (b and (b < i))
345
- Rubyvis.rgb(
346
- [255, (r/k).floor].min,
347
- [255, (g/k).floor].min,
348
- [255, (b/k).floor].min,
349
- a)
332
+ k = 0.7**k
333
+ i = 30
334
+ r=self.r
335
+ g=self.g
336
+ b=self.b
337
+ return Rubyvis.rgb(i, i, i, a) if (!r and !g and !b)
338
+ r = i if (r and (r < i))
339
+ g = i if (g and (g < i))
340
+ b = i if (b and (b < i))
341
+ Rubyvis.rgb(
342
+ [255, (r/k).floor].min,
343
+ [255, (g/k).floor].min,
344
+ [255, (b/k).floor].min,
345
+ a)
350
346
  end
351
347
  # Returns a new color that is a darker version of this color. This method
352
348
  # applies an arbitrary scale factor to each of the three RGB components of this
@@ -368,7 +364,6 @@ module Rubyvis
368
364
  end
369
365
  # Represents a color in HSL space.
370
366
  class Hsl < Color
371
-
372
367
  # The hue, an integer in [0, 360].
373
368
  attr_accessor :h
374
369
  # The saturation, a float in [0, 1].
@@ -386,6 +381,9 @@ module Rubyvis
386
381
  @l=l
387
382
  @a=a
388
383
  end
384
+ def ==(v)
385
+ self.class==v.class and @h==v.h and @s==v.s and @l==v.l and @a==v.a
386
+ end
389
387
 
390
388
  # Returns the RGB color equivalent to this HSL color.
391
389
  def rgb
@@ -416,7 +414,7 @@ module Rubyvis
416
414
  return m1
417
415
  }
418
416
  vv=lambda {|h1|
419
- (v(h1) * 255).round
417
+ (v.call(h1) * 255).round
420
418
  }
421
419
 
422
420
  Rubyvis.rgb(vv.call(h + 120), vv.call(h), vv.call(h - 120), a)
@@ -46,7 +46,7 @@ module Rubyvis
46
46
  i = Array.new(mins - i.size + 1).join(@padi) + i.to_s
47
47
  end
48
48
 
49
- s[0] = x < 0 ? np + i + ns : i
49
+ s[0] = x < 0 ? @np + i + @ns : i
50
50
 
51
51
  #/* Pad the fractional part. */
52
52
  f = s[1].nil? ? "" : s[1]
@@ -23,7 +23,7 @@ module Rubyvis
23
23
  end
24
24
  @properties[name]=true
25
25
  self.property_method(name,false, func, self)
26
- define_method(name.to_s+"=") {|v|
26
+ define_method(name.to_s + "=") {|v|
27
27
  self.send(name,v)
28
28
  }
29
29
  end
@@ -32,6 +32,7 @@ module Rubyvis
32
32
  end
33
33
 
34
34
  require 'rubyvis/layout/stack'
35
+ require 'rubyvis/layout/horizon'
35
36
  require 'rubyvis/layout/grid'
36
37
  require 'rubyvis/layout/network'
37
38
  require 'rubyvis/layout/hierarchy'
@@ -41,5 +42,5 @@ require 'rubyvis/layout/partition'
41
42
  require 'rubyvis/layout/cluster'
42
43
  require 'rubyvis/layout/indent'
43
44
  require 'rubyvis/layout/pack'
44
-
45
+ require 'rubyvis/layout/arc'
45
46
 
@@ -0,0 +1,188 @@
1
+ module Rubyvis
2
+ class Layout
3
+ # Alias for Rubyvis::Layout::Arc
4
+ def self.Arc
5
+ Rubyvis::Layout::Arc
6
+ end
7
+
8
+ # @class Implements a layout for arc diagrams. An arc diagram is a network
9
+ # visualization with a one-dimensional layout of nodes, using circular arcs to
10
+ # render links between nodes. For undirected networks, arcs are rendering on a
11
+ # single side; this makes arc diagrams useful as annotations to other
12
+ # two-dimensional network layouts, such as rollup, matrix or table layouts. For
13
+ # directed networks, links in opposite directions can be rendered on opposite
14
+ # sides using <tt>directed(true)</tt>.
15
+ #
16
+ # <p>Arc layouts are particularly sensitive to node ordering; for best results,
17
+ # order the nodes such that related nodes are close to each other. A poor
18
+ # (e.g., random) order may result in large arcs with crossovers that impede
19
+ # visual processing. A future improvement to this layout may include automatic
20
+ # reordering using, e.g., spectral graph layout or simulated annealing.
21
+ #
22
+ # <p>This visualization technique is related to that developed by
23
+ # M. Wattenberg, <a
24
+ # href="http://www.research.ibm.com/visual/papers/arc-diagrams.pdf">"Arc
25
+ # Diagrams: Visualizing Structure in Strings"</a> in <i>IEEE InfoVis</i>, 2002.
26
+ # However, this implementation is limited to simple node-link networks, as
27
+ # opposed to structures with hierarchical self-similarity (such as strings).
28
+ #
29
+ # <p>As with other network layouts, three mark prototypes are provided:<ul>
30
+ #
31
+ # <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Dot}.
32
+ # <li><tt>link</tt> - for rendering links; typically a {@link pv.Line}.
33
+ # <li><tt>label</tt> - for rendering node labels; typically a {@link pv.Label}.
34
+ #
35
+ # </ul>For more details on how this layout is structured and can be customized,
36
+ # see {@link pv.Layout.Network}.
37
+
38
+ class Arc < Network
39
+ @properties=Network.properties.dup
40
+ attr_accessor :_interpolate, :_directed, :_reverse
41
+ def initialize
42
+ super
43
+ @_interpolate=nil # cached interpolate
44
+ @_directed=nil # cached directed
45
+ @_reverse=nil # cached reverse
46
+ @_sort=nil
47
+ that=self
48
+ @link.data(lambda {|_p|
49
+ s=_p.source_node;t=_p.target_node
50
+ that._reverse != (that._directed or (s.breadth < t.breadth)) ? [s, t] : [t, s]
51
+ }).interpolate(lambda{ that._interpolate})
52
+ end
53
+
54
+ def build_implied(s)
55
+ return true if network_build_implied(s)
56
+ # Cached
57
+
58
+ nodes = s.nodes
59
+ orient = s.orient
60
+ sort = @_sort
61
+ index = Rubyvis.range(nodes.size)
62
+ w = s.width
63
+ h = s.height
64
+ r = [w,h].min / 2.0
65
+ # /* Sort the nodes. */
66
+ if (sort)
67
+ index.sort! {|a,b| sort.call(nodes[a],nodes[b])}
68
+ end
69
+
70
+
71
+
72
+ #/** @private Returns the mid-angle, given the breadth. */
73
+ mid_angle=lambda do |b|
74
+ case orient
75
+ when "top"
76
+ -Math::PI / 2.0
77
+ when "bottom"
78
+ Math::PI / 2.0
79
+ when "left"
80
+ Math::PI
81
+ when "right"
82
+ 0
83
+ when "radial"
84
+ (b - 0.25) * 2.0 * Math::PI
85
+ end
86
+ end
87
+
88
+ # /** @private Returns the x-position, given the breadth. */
89
+ x= lambda do |b|
90
+ case orient
91
+ when "top"
92
+ b * w
93
+ when "bottom"
94
+ b * w
95
+ when "left"
96
+ 0;
97
+ when "right"
98
+ w;
99
+ when "radial"
100
+ w / 2.0 + r * Math.cos(mid_angle.call(b))
101
+ end
102
+ end
103
+
104
+ # /** @private Returns the y-position, given the breadth. */
105
+ y=lambda do |b|
106
+ case orient
107
+ when "top"
108
+ 0;
109
+ when "bottom"
110
+ h;
111
+ when "left"
112
+ b* h
113
+ when "right"
114
+ b * h
115
+ when "radial"
116
+ h / 2.0 + r * Math.sin(mid_angle.call(b))
117
+ end
118
+ end
119
+
120
+ #/* Populate the x, y and mid-angle attributes. */
121
+ nodes.each_with_index do |nod, i|
122
+ n=nodes[index[i]]
123
+ n.breadth=(i+0.5) / nodes.size
124
+ b=n.breadth
125
+ n.x=x[b]
126
+ n.y=y[b]
127
+ n.mid_angle=mid_angle[b]
128
+ end
129
+
130
+ @_directed = s.directed
131
+ @_interpolate = s.orient == "radial" ? "linear" : "polar"
132
+ @_reverse = s.orient == "right" or s.orient == "top"
133
+
134
+ end
135
+
136
+ ##
137
+ # :attr: orient
138
+ # The orientation. The default orientation is "left", which means that nodes
139
+ # will be positioned from left-to-right in the order they are specified in the
140
+ # <tt>nodes</tt> property. The following orientations are supported:<ul>
141
+ #
142
+ # <li>left - left-to-right.
143
+ # <li>right - right-to-left.
144
+ # <li>top - top-to-bottom.
145
+ # <li>bottom - bottom-to-top.
146
+ # <li>radial - radially, starting at 12 o'clock and proceeding clockwise.</ul>
147
+
148
+ ##
149
+ # :attr: directed
150
+ # Whether this arc digram is directed (bidirectional); only applies to
151
+ # non-radial orientations. By default, arc digrams are undirected, such that
152
+ # all arcs appear on one side. If the arc digram is directed, then forward
153
+ # links are drawn on the conventional side (the same as as undirected
154
+ # links--right, left, bottom and top for left, right, top and bottom,
155
+ # respectively), while reverse links are drawn on the opposite side.
156
+
157
+ attr_accessor_dsl :orient, :directed
158
+
159
+ def self.defaults
160
+ Arc.new.mark_extend(Network.defaults).
161
+ orient("bottom")
162
+ end
163
+
164
+ # Specifies an optional sort function. The sort function follows the same
165
+ # comparator contract required by {@link pv.Dom.Node#sort}. Specifying a sort
166
+ # function provides an alternative to sort the nodes as they are specified by
167
+ # the <tt>nodes</tt> property; the main advantage of doing this is that the
168
+ # comparator function can access implicit fields populated by the network
169
+ # layout, such as the <tt>linkDegree</tt>.
170
+ #
171
+ # <p>Note that arc diagrams are particularly sensitive to order. This is
172
+ # referred to as the seriation problem, and many different techniques exist to
173
+ # find good node orders that emphasize clusters, such as spectral layout and
174
+ # simulated annealing.
175
+ #
176
+ # @param {function} f comparator function for nodes.
177
+ # @returns {pv.Layout.Arc} this.
178
+ def sort(f=nil,&block)
179
+ f||=block
180
+ @_sort=f
181
+ self
182
+ end
183
+
184
+
185
+ end
186
+ end
187
+ end
188
+
@@ -0,0 +1,153 @@
1
+ module Rubyvis
2
+ class Layout
3
+ # Alias for Rubyvis::Layout::Horizon
4
+ def self.Horizon
5
+ Rubyvis::Layout::Horizon
6
+ end
7
+
8
+ # Implements a horizon layout, which is a variation of a single-series
9
+ # area chart where the area is folded into multiple bands. Color is used to
10
+ # encode band, allowing the size of the chart to be reduced significantly
11
+ # without impeding readability. This layout algorithm is based on the work of
12
+ # J. Heer, N. Kong and M. Agrawala in <a href="http://hci.stanford.edu/publications/2009/heer-horizon-chi09.pdf">"Sizing
13
+ # the Horizon: The Effects of Chart Size and Layering on the Graphical
14
+ # Perception of Time Series Visualizations"</a>, CHI 2009.
15
+ #
16
+ # <p>This layout exports a single <tt>band</tt> mark prototype, which is
17
+ # intended to be used with an area mark. The band mark is contained in a panel
18
+ # which is replicated per band (and for negative/positive bands). For example,
19
+ # to create a simple horizon graph given an array of numbers:
20
+ #
21
+ # vis.add(Rubyvis::Layout::Horizon)
22
+ # .bands(n)
23
+ # .band.add(Rubyvis::Area)
24
+ # .data(data)
25
+ # .left(lambda { index * 35})
26
+ # .height(lambda {|d| d * 40})
27
+ #
28
+ # The layout can be further customized by changing the number of bands, and
29
+ # toggling whether the negative bands are mirrored or offset. (See the
30
+ # above-referenced paper for guidance.)
31
+ #
32
+ # <p>The <tt>fill_style</tt> of the area can be overridden, though typically it
33
+ # is easier to customize the layout's behavior through the custom
34
+ # <tt>background_style</tt>, <tt>positive_style</tt> and <tt>negative_style</tt>
35
+ # properties. By default, the background is white, positive bands are blue, and
36
+ # negative bands are red. For the most accurate presentation, use fully-opaque
37
+ # colors of equal intensity for the negative and positive bands.
38
+ class Horizon < Layout
39
+ @properties=Layout.properties.dup
40
+ # The band prototype. This prototype is intended to be used with an Area
41
+ # mark to render the horizon bands.
42
+ attr_accessor :band
43
+ attr_accessor :_bands, :_mode, :_size, :_fill, :_red, :_blue
44
+ def initialize
45
+ super
46
+ @_bands=nil
47
+ @_mode=nil # cached mode
48
+ @_size=nil # cached height
49
+ @_fill=nil # cached background style
50
+ @_red=nil # cached negative color (ramp)
51
+ @_blue=nil # cached positive color (ramp)
52
+ @_bands_panel=_bands_panel
53
+ @band=_band
54
+ end
55
+ def build_implied(s)
56
+ layout_build_implied(s)
57
+ @_bands=s.bands
58
+ @_mode=s.mode
59
+ @_size=((@_mode == "color" ? 0.5 : 1) * s.height).round
60
+ @_fill=s.background_style
61
+ @_red=Rubyvis.ramp(@_fill, s.negative_style).domain(0,@_bands)
62
+ @_blue=Rubyvis.ramp(@_fill, s.positive_style).domain(0,@_bands)
63
+
64
+ end
65
+ def _bands_panel
66
+ that=self
67
+ Rubyvis::Panel.new().
68
+ data(lambda {Rubyvis.range(that._bands.to_f * 2)}).
69
+ overflow("hidden").
70
+ height(lambda {that._size}).
71
+ top(lambda {|i| that._mode=='color' ? (i & 1) * that._size : 0 }).
72
+ fill_style(lambda {|i| i!=0 ? nil : that._fill})
73
+ end
74
+
75
+ def _band
76
+ that=self
77
+ m=Rubyvis::Mark.new().
78
+ top(lambda {|d,i|
79
+ (that._mode == "mirror" and (i & 1)!=0) ? (i + 1 >> 1) * that._size : nil
80
+ }).
81
+ bottom(lambda {|d,i|
82
+ crit= (i & 1)!= 0 ? i & 1 : -1
83
+ (that._mode == "mirror") ? ((i & 1)!=0 ? nil : (i + 1 >> 1) * -that._size) : (crit * (i + 1 >> 1) * that._size)
84
+ }).
85
+ fill_style(lambda {|d,i|
86
+ ((i & 1)!=0 ? that._red : that._blue).scale((i >> 1) + 1)
87
+ })
88
+
89
+ class << m # :nodoc:
90
+ def that_and_bands(that,bands)
91
+ @that = that
92
+ @bands=bands
93
+ end
94
+ def add(type)
95
+ bands=@bands
96
+ that = @that
97
+ that.add( Rubyvis.Panel ).mark_extend(bands). add(type). mark_extend(self)
98
+ end
99
+ end
100
+
101
+ m.that_and_bands(self, @_bands_panel)
102
+ m
103
+ end
104
+
105
+ ##
106
+ # :attr: mode
107
+ # The horizon mode: offset, mirror, or color. The default is "offset".
108
+
109
+ ##
110
+ # :attr: bands
111
+ # The number of bands. Must be at least one. The default value is two.
112
+ #
113
+
114
+ ##
115
+ # :attr: positive_style
116
+ # The positive band color; if non-null, the interior of positive bands are
117
+ # filled with the specified color. The default value of this property is blue.
118
+ # For accurate blending, this color should be fully opaque.
119
+ #
120
+
121
+ ##
122
+ # :attr: negative_style
123
+ # The negative band color; if non-null, the interior of negative bands are
124
+ # filled with the specified color. The default value of this property is red.
125
+ # For accurate blending, this color should be fully opaque.
126
+ #
127
+
128
+ ##
129
+ # :attr: background_style
130
+ # The background color. The panel background is filled with the specified
131
+ # color, and the negative and positive bands are filled with an interpolated
132
+ # color between this color and the respective band color. The default value of
133
+ # this property is white. For accurate blending, this color should be fully
134
+ # opaque.
135
+ #
136
+
137
+ attr_accessor_dsl :bands, :mode, :background_style, [:background_style, lambda {|d| Rubyvis.color(d)}], [:positive_style, lambda {|d| Rubyvis.color(d)}], [:negative_style, lambda {|d| Rubyvis.color(d)}]
138
+
139
+ # Default properties for horizon layouts. By default, there are two bands, the
140
+ # mode is "offset", the background style is "white", the positive style is
141
+ # blue, negative style is red.
142
+ def self.defaults
143
+ Horizon.new.mark_extend(Layout.defaults).
144
+ bands(2).
145
+ mode('offset').
146
+ background_style('white').
147
+ positive_style('#1f77b4').
148
+ negative_style('#d62728')
149
+ end
150
+ end
151
+ end
152
+ end
153
+