rubyvis 0.3.4 → 0.3.5

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