chatchart 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ this code is licensed under the "don't be a retarded asshole" license.
2
+ if i don't like how you use this software i can tell you to fuck off
3
+ and you can't use it, otherwise you can use it.
data/README ADDED
@@ -0,0 +1,37 @@
1
+ ChatChart - ASCII Graph Layout & Drawing
2
+
3
+ This is a package of various crappy ruby code which generally
4
+ revolves around ASCII drawing, graph layout, and physics simulation
5
+
6
+ Copyright(c) 2009 by Christopher Abad
7
+
8
+ EMAIL: aempirei@gmail.com
9
+ AIM: ambientempire
10
+ IRC: aempirei on irc.freenode.net
11
+
12
+ http://blog.twentygoto10.com/
13
+ http://www.twentygoto10.com/
14
+ http://www.the-mathclub.net/
15
+
16
+ this code is licensed under the "don't be a retarded asshole" license.
17
+ if i don't like how you use this software i can tell you t
18
+
19
+ == RUNME ==
20
+
21
+ check out the demos in the ./demos directory
22
+
23
+ demo-automatic.rb - a simple automatic graph layout sample driver
24
+ demo-animated.rb - an animated automatic graph layout driver using somewhat of
25
+ a molecular physics model
26
+ demo-physics.rb - gravity/mass physics demo
27
+ demo-simple.rb - simple random graph layout and ascii drawing driver
28
+
29
+ == USEME ==
30
+
31
+ chatchart.rb - ASCII drawing module
32
+ point.rb - 2D point class
33
+ particle.rb - particle class
34
+
35
+ == HOWTO ==
36
+
37
+ read the samples for now. but i promise i will include more information soon.
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # autograph.rb - Animated Autographer Sample
4
+ #
5
+ # this will do some animated automatic graph layout.
6
+ # make sure your terminal is the right size.
7
+ # i think the drawing window is set to 70x30
8
+ #
9
+ # Copyright(c) 2009 by Christopher Abad
10
+ # aempirei@gmail.com
11
+ # http://www.twentygoto10.com/
12
+ #
13
+ # this code is licensed under the "don't be a retarded asshole" license.
14
+ # if i don't like how you use this software i can tell you to fuck off
15
+ # and you can't use it, otherwise you can use it.
16
+ #
17
+ # == Usage ==
18
+ #
19
+ # autograph.rb [OPTIONS]
20
+ #
21
+ # -h, --help:
22
+ # show help
23
+ #
24
+ # -1, --taxicab:
25
+ # draw using taxicab topology
26
+ #
27
+ # -2, --hockey:
28
+ # draw using hockey-stick topology
29
+ #
30
+
31
+ require 'rubygems'
32
+ require 'chatchart'
33
+ require 'getoptlong'
34
+ require 'rdoc/usage'
35
+
36
+ g = ChatChart::Graph.new << [
37
+ :course - :name0 ,
38
+ :course - :code ,
39
+ :course - :'C-I' ,
40
+ :course - :'S-C' ,
41
+ :institute - :name1 ,
42
+ :institute - :'S-I' ,
43
+ :institute - :'C-I' ,
44
+ :student - :grade ,
45
+ :student - :name2 ,
46
+ :student - :number ,
47
+ :student - :'S-C' ,
48
+ :student - :'S-I' ,
49
+ :a - :b,
50
+ :a - :c,
51
+ :a - :d,
52
+ :b - :q,
53
+ :b - :w,
54
+ :b - :e,
55
+ ]
56
+
57
+ opts = GetoptLong.new(
58
+ [ '--help' , '-h', GetoptLong::NO_ARGUMENT ],
59
+ [ '--hockey' , '-2', GetoptLong::NO_ARGUMENT ],
60
+ [ '--taxicab', '-1', GetoptLong::NO_ARGUMENT ]
61
+ )
62
+
63
+ linestyle = ChatChart::L1Line
64
+
65
+ opts.each do |opt,arg|
66
+ case opt
67
+ when '--hockey'
68
+ linestyle = ChatChart::HLine
69
+ when '--taxicab'
70
+ linestyle = ChatChart::L1Line
71
+ when '--help'
72
+ RDoc::usage
73
+ end
74
+ end
75
+
76
+ COLS = 70
77
+ ROWS = 30
78
+
79
+ HOME = "\033[H"
80
+ BOLD = "\033[1m"
81
+ NORM = "\033[0m"
82
+ CLRSCR = "\033[2J"
83
+
84
+ CURSOR_ON = "\033[?25h"
85
+ CURSOR_OFF = "\033[?25l"
86
+
87
+ WINDOW = [ ChatChart::P[0,0], ChatChart::P[COLS,ROWS] ]
88
+
89
+ Kernel::at_exit { puts CURSOR_ON + "\033[50;1H" + NORM }
90
+
91
+ Kernel::trap('INT') { |signo| exit }
92
+
93
+ puts CURSOR_OFF + CLRSCR
94
+
95
+ # loop do
96
+
97
+ ChatChart::RandomLayout[ g, COLS, ROWS ]
98
+
99
+ C_SZ = 50
100
+ es = []
101
+ ses = 0
102
+
103
+ loop do
104
+ ChatChart::SmartLayout[ g, 1 ]
105
+ es << g.energy
106
+ if es.length >= C_SZ
107
+ es = es[-C_SZ,C_SZ]
108
+ pses = ses
109
+ ses = es[-C_SZ,C_SZ].inject(0.0) { |sum,e| sum += e }.to_f / C_SZ
110
+ de = ses - pses
111
+ else
112
+ ses = 0
113
+ de = 0
114
+ end
115
+
116
+ c = g.to_canvas(linestyle) << ChatChart::Title[ "delta-energy: %+.3f" % [ de ], proc { |p| p.r }, 1, 1 ]
117
+ puts HOME + c.window(*WINDOW)
118
+ end
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # autograph-simple.rb - Simple AutoGrapher Sample Driver
4
+ #
5
+ # Copyright(c) 2009 by Christopher Abad
6
+ # aempirei@gmail.com
7
+ # http://www.twentygoto10.com/
8
+ #
9
+ # this code is licensed under the "don't be a retarded asshole" license.
10
+ # if i don't like how you use this software i can tell you to fuck off
11
+ # and you can't use it, otherwise you can use it.
12
+ #
13
+
14
+ require 'rubygems'
15
+ require 'chatchart'
16
+
17
+ g = ChatChart::Graph.new << [
18
+ :course - :name0 ,
19
+ :course - :code ,
20
+ :course - :'C-I' ,
21
+ :course - :'S-C' ,
22
+ :institute - :name1 ,
23
+ :institute - :'S-I' ,
24
+ :institute - :'C-I' ,
25
+ :student - :grade ,
26
+ :student - :name2 ,
27
+ :student - :number ,
28
+ :student - :'S-C' ,
29
+ :student - :'S-I' ,
30
+ :a - :b,
31
+ :a - :c,
32
+ :a - :d,
33
+ :b - :q,
34
+ :b - :w,
35
+ :b - :e,
36
+ ]
37
+
38
+ puts "be patient, this is ruby code..."
39
+ ChatChart::SmartLayout[ g ]
40
+ puts g.to_canvas(ChatChart::L1Line)
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # fizzix.rb - Physics Test Code
4
+ #
5
+ # Copyright(c) 2009 by Christopher Abad
6
+ # aempirei@gmail.com
7
+ # http://www.twentygoto10.com/
8
+ #
9
+ # this code is licensed under the "don't be a retarded asshole" license.
10
+ # if i don't like how you use this software i can tell you to fuck off
11
+ # and you can't use it, otherwise you can use it.
12
+ #
13
+
14
+ require 'rubygems'
15
+ require 'chatchart'
16
+ require 'particle'
17
+
18
+ include ChatChart
19
+
20
+ COLS = 100
21
+ ROWS = 30
22
+ P_SZ = 10
23
+
24
+ HOME = "\033[H"
25
+ BOLD = "\033[1m"
26
+ NORM = "\033[0m"
27
+
28
+ CURSOR_ON = "\033[?25h"
29
+ CURSOR_OFF = "\033[?25l"
30
+
31
+ WINDOW = [ P[0,0], P[COLS,ROWS] ]
32
+
33
+ ps = []
34
+
35
+ P_SZ.times { ps << GParticle.new(P.random(COLS, ROWS).vector) }
36
+
37
+ c = Canvas.new
38
+
39
+ Kernel::at_exit { puts CURSOR_ON + "\033[50;1H" + NORM }
40
+ Kernel::trap('INT') { |signo| exit }
41
+
42
+ puts CURSOR_OFF + BOLD
43
+
44
+ loop do
45
+ ps.each { |p| c << Dot[' ', p.p.quantize] }
46
+ # calculate the instantaneous acceleration
47
+ ps.each { |p| p.a = p.n_body_acceleration(ps.reject { |q| q.object_id == p.object_id }) }
48
+ # update the velocities
49
+ ps.each { |p| p.v += p.a * GParticle::DT ; p.v *= GParticle::Z }
50
+ # project new positions
51
+ ps.each { |p| p.p += p.v * GParticle::DT }
52
+ ps.each { |p| c << Dot['@', p.p.quantize] }
53
+ puts HOME + c.window(*WINDOW)
54
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # sample.rb - ChatChart Sample Driver
4
+ #
5
+ # Copyright(c) 2009 by Christopher Abad
6
+ # aempirei@gmail.com
7
+ # http://www.twentygoto10.com/
8
+ #
9
+ # this code is licensed under the "don't be a retarded asshole" license.
10
+ # if i don't like how you use this software i can tell you to fuck off
11
+ # and you can't use it, otherwise you can use it.
12
+ #
13
+
14
+ require 'rubygems'
15
+ require 'chatchart'
16
+
17
+ COLS = 60
18
+ ROWS = 14
19
+
20
+ g = ChatChart::Graph.new << [
21
+ :course - :name0 ,
22
+ :course - :code ,
23
+ :course - :'C-I' ,
24
+ :course - :'S-C' ,
25
+ :institute - :name1 ,
26
+ :institute - :'S-I' ,
27
+ :institute - :'C-I' ,
28
+ :student - :grade ,
29
+ :student - :name2 ,
30
+ :student - :number ,
31
+ :student - :'S-C' ,
32
+ :student - :'S-I' ,
33
+ ]
34
+
35
+ ChatChart::RandomLayout[ g, COLS, ROWS ]
36
+
37
+ print g.to_canvas << ChatChart::Title[ 'SHIT FOR BRAINS'.reverse, proc { |p| p.l.d }, -2, 0 ]
data/lib/chatchart.rb ADDED
@@ -0,0 +1,518 @@
1
+ #
2
+ # ChatChart
3
+ # chatchart.rb - ASCII Graphs
4
+ #
5
+ # use this to create an ascii graph described using a simple array of
6
+ # vertex adjacencies. just check out the sample driver (sample.rb)
7
+ #
8
+ # Copyright(c) 2009 by Christopher Abad
9
+ # aempirei@gmail.com
10
+ # http://www.twentygoto10.com/
11
+ #
12
+ # this code is licensed under the "don't be a retarded asshole" license.
13
+ # if i don't like how you use this software i can tell you to fuck off
14
+ # and you can't use it, otherwise you can use it.
15
+ #
16
+
17
+ require 'point'
18
+ require 'particle'
19
+
20
+ class Symbol
21
+ def <=>(b)
22
+ self.to_s <=> b.to_s
23
+ end
24
+ def -(b)
25
+ ChatChart::Edge[self,b]
26
+ end
27
+ end
28
+
29
+ class Circuit
30
+ def self.[](p)
31
+ edges = []
32
+ p.split(//).each_cons(2) { |a,b| edges << a.to_sym - b.to_sym }
33
+ edges
34
+ end
35
+ end
36
+
37
+ module ChatChart
38
+
39
+ # a canvas is the primary drawing space
40
+
41
+ class Canvas < Hash
42
+ def [](p)
43
+ raise 'non-point' unless p.is_a? P
44
+ super(p)
45
+ end
46
+
47
+ def <<(g)
48
+ case g
49
+ when Proc
50
+ g.call(self)
51
+ else
52
+ raise 'non-curve-proc' unless g < Curve
53
+ end
54
+ self
55
+ end
56
+
57
+ def []=(p,v)
58
+ raise 'non-point' unless p.is_a? P
59
+ raise 'non-char' unless v.is_a?(String) and v.length == 1
60
+ super(p,v)
61
+ end
62
+
63
+ def bounds
64
+ xs = keys.map { |p| p.x }.sort
65
+ ys = keys.map { |p| p.y }.sort
66
+
67
+ return [ P[xs.first,ys.first], P[xs.last,ys.last] ]
68
+ end
69
+
70
+ def window(ul,lr)
71
+ str = ''
72
+ ul = ul.quantize
73
+ lr = lr.quantize
74
+ ((ul.y)..(lr.y)).each do |y|
75
+ ((ul.x)..(lr.x)).each do |x|
76
+ p = P[x,y]
77
+ if has_key?(p)
78
+ str << self[p]
79
+ else
80
+ str << ' '
81
+ end
82
+ end
83
+ str << "\n"
84
+ end
85
+ return str
86
+ end
87
+
88
+ def to_s
89
+ window *bounds
90
+ end
91
+ end
92
+
93
+ # the curve is a general path through the canvas space, which consists of some geometric description
94
+ # and the [] operator which returns a proc that accepts a single canvas parameter which is then drawn to
95
+
96
+ class Curve
97
+ def self.[](*geometry)
98
+ proc { |canvas| self.draw(canvas, *geometry) }
99
+ end
100
+ def self.draw(canvas, *geometry)
101
+ raise 'undefined method'
102
+ end
103
+ def self.get_point(*geometry)
104
+ case geometry.first
105
+ when P
106
+ geometry.first
107
+ when Integer
108
+ P[geometry.first, geometry.last]
109
+ else
110
+ raise 'invalid-geometry'
111
+ end
112
+ end
113
+ def self.get_points(*geometry)
114
+ return [] if geometry.empty?
115
+ case geometry.first
116
+ when P
117
+ [ geometry.shift.dup ] + get_points(*geometry)
118
+ when Integer
119
+ [ P[geometry.shift, geometry.shift] ] + get_points(*geometry)
120
+ else
121
+ raise 'invalid-geometry'
122
+ end
123
+ end
124
+ def self.get_interval(*geometry)
125
+ points = get_points(*geometry)
126
+ points.first .. points.last
127
+ end
128
+ end
129
+
130
+ # titles are just an instance of a general curve, where there is
131
+ # some geometry describing their origin and a block describing
132
+ # their differential motion
133
+
134
+ class Title < Curve
135
+ def self.[](title, blk, *geometry)
136
+ proc { |canvas| self.draw(canvas, title, *geometry, &blk) }
137
+ end
138
+
139
+ def self.draw(canvas, title, *geometry, &blk)
140
+ p = get_point(*geometry).quantize
141
+ title.to_s.split(//).each do |ch|
142
+ canvas[p] = ch
143
+ p = blk.call(p)
144
+ end
145
+ end
146
+ end
147
+
148
+ class Dot < Curve
149
+ def self.[](dot, *geometry)
150
+ proc { |canvas| self.draw(canvas, dot, *geometry) }
151
+ end
152
+
153
+ def self.draw(canvas, dot, *geometry)
154
+ p = get_point(*geometry)
155
+ canvas[p] = dot
156
+ end
157
+ end
158
+
159
+ # lines are just an instance of a general curve, where there is some geometry describing their path
160
+
161
+ class Line < Curve
162
+ def self.[](*geometry)
163
+ proc { |canvas| self.draw(canvas, *geometry) }
164
+ end
165
+ end
166
+
167
+ # L1-norm (taxicab norm) lines are drawn up/down then left/right from origin (a) to destination (b)
168
+
169
+ class L1Line < Line
170
+ def self.draw(canvas, *geometry)
171
+
172
+ a,b = get_points(*geometry)
173
+ a.quantize!
174
+ b.quantize!
175
+ c = a % b
176
+
177
+ y1,y2 = [a.y,c.y].sort
178
+
179
+ (y1..y2).each do |y|
180
+ p = P[c.x,y]
181
+ next if p == c
182
+ canvas[p] = '|'
183
+ end
184
+
185
+ x1,x2 = [b.x,c.x].sort
186
+
187
+ (x1..x2).each do |x|
188
+ p = P[x,c.y]
189
+ next if p == c
190
+ canvas[p] = '-'
191
+ end
192
+
193
+ canvas[c] = '+'
194
+ canvas[a] = 'o'
195
+ canvas[b] = 'o'
196
+ end
197
+ end
198
+
199
+ # a hockey line is somewhere between an L1-norm and L2-norm topology where the topology
200
+ # is constructed from 0, 45, and 90 degree edges with unit projections onto x and y and
201
+ # where length of a vector v is calculated as follows:
202
+ # v = <dx,dy>
203
+ # dz = min(dx,dy)
204
+ # dw = |dx-dy|
205
+ # |v| = |<dz,dz>|+dw
206
+
207
+ class HLine < Line
208
+ def self.draw(canvas, *geometry)
209
+
210
+ a,b = get_points(*geometry)
211
+ a.quantize!
212
+ b.quantize!
213
+
214
+ v = b-a
215
+
216
+ amstep = (v.proj(P::J).unit + v.proj(P::I).unit)
217
+ amstep.quantize!
218
+ am = amstep * v.norm(:min)
219
+ am.quantize!
220
+
221
+ mb = v-am
222
+ mbstep = mb.unit.quantize
223
+
224
+ pos = a
225
+
226
+ while pos != a + am
227
+ pos += amstep
228
+ canvas[pos] = (amstep.monic == P[1,1]) ? '\\' : '/'
229
+ end
230
+
231
+ while pos != b
232
+ pos += mbstep
233
+ canvas[pos] = (mbstep.x == 0.0) ? '|' : '-'
234
+ end
235
+
236
+ canvas[a + am] = '+'
237
+ canvas[a] = 'o'
238
+ canvas[b] = 'o'
239
+ end
240
+ end
241
+
242
+ class WiggleLine < Line
243
+ end
244
+
245
+ class Edge
246
+ attr_accessor :from
247
+ attr_accessor :to
248
+
249
+ def self.[](a,b)
250
+ new(a,b)
251
+ end
252
+
253
+ def initialize(a,b)
254
+ raise 'invalid-edge' unless a.is_a? Symbol and b.is_a? Symbol
255
+ @from,@to = [a,b].sort
256
+ end
257
+
258
+ def name
259
+ "#{from} -- #{to}".to_sym
260
+ end
261
+
262
+ def to_a
263
+ [from,to]
264
+ end
265
+
266
+ def hash
267
+ to_a.hash
268
+ end
269
+
270
+ def neighbor(v)
271
+ v == to ? from : to
272
+ end
273
+
274
+ # check for edge endpoint inclusion of a vertex (or by name of vertex)
275
+ def ===(v)
276
+ case v
277
+ when Array
278
+ v.inject(false) { |acc,w| acc ||= (self === w) }
279
+ when Vertex
280
+ self === v.name
281
+ when Symbol
282
+ @from == v or @to == v
283
+ else
284
+ raise 'invalid-comparison'
285
+ end
286
+ end
287
+ # compare two edges (undirected)
288
+ def ==(e)
289
+ raise 'invalid-comparison' unless e.is_a? Edge
290
+ from == e.from and to == e.to
291
+ end
292
+ end
293
+
294
+ class Vertex
295
+ attr_reader :p
296
+ attr_reader :name
297
+ def initialize(name)
298
+ raise 'non-symbol' unless name.is_a? Symbol
299
+ @p = Particle[]
300
+ @name = name
301
+ end
302
+ def initialize_copy(orig)
303
+ @p = @p.dup
304
+ end
305
+ def hash
306
+ @name.hash
307
+ end
308
+ # compare vertices, which allows for comparison against symbols, which is the vertex name type
309
+ def ===(v)
310
+ case v
311
+ when Array
312
+ v.inject(false) { |acc,w| acc ||= (self === w) }
313
+ when Symbol
314
+ self.name == v
315
+ when Vertex
316
+ self == v
317
+ else
318
+ raise 'invalid-comparison'
319
+ end
320
+ end
321
+ def ==(v)
322
+ raise 'invalid-comparison' unless v.is_a? Vertex
323
+ self.name == v.name
324
+ end
325
+ end
326
+
327
+ # graphs are collections of vertices and edges without layout information
328
+
329
+ class Graph
330
+ attr_reader :e # edges
331
+ attr_reader :v # vertices
332
+ attr_accessor :root # root node name
333
+ def initialize(root=nil)
334
+ @e = {}
335
+ @v = {}
336
+ @root = case root
337
+ when Symbol
338
+ root
339
+ when Vertex
340
+ root.name
341
+ when NilClass
342
+ # do nothing
343
+ else
344
+ raise 'invalid-root-node'
345
+ end
346
+ self << root if root
347
+ end
348
+ def initialize_copy(orig)
349
+ @e = @e.dup
350
+ @v = @v.dup
351
+ end
352
+ def <<(elem)
353
+ case elem
354
+ when Graph
355
+ self << elem.v.values << elem.e.values
356
+ when Array
357
+ elem.each { |q| self << q }
358
+ when Edge
359
+ @e[elem.name] = elem.dup
360
+ self << elem.from unless @v[elem.from]
361
+ self << elem.to unless @v[elem.to]
362
+ when Vertex
363
+ @v[elem.name] = elem.dup
364
+ when Symbol
365
+ self << Vertex.new(elem)
366
+ when NilClass
367
+ # do nothing
368
+ else
369
+ raise 'non-graph-element'
370
+ end
371
+ self
372
+ end
373
+ def to_s
374
+ v_str = @v.keys.map { |s| "\t#{s};\n" }.join
375
+ e_str = @e.keys.map { |s| "\t#{s};\n" }.join
376
+ "#{self.class.name.downcase} g {\n#{e_str}#{v_str}};\n"
377
+ end
378
+ def to_canvas(linestyle = L1Line)
379
+ canvas = Canvas.new
380
+ @e.values.each { |e| canvas << linestyle[ @v[e.from].p.p, @v[e.to].p.p ] }
381
+ @v.values.each { |v| canvas << Title[ v.name, proc { |p| p.r }, v.p.p ] }
382
+ canvas
383
+ end
384
+
385
+ def find_edges(*vs)
386
+ @e.values.select { |e| e === vs }
387
+ end
388
+
389
+ def neighborhood(v)
390
+ find_edges(v).map { |e| @v[e.neighbor(v)] }
391
+ end
392
+
393
+ def [](*vs)
394
+ Graph.new(vs.first) << find_edges(*vs)
395
+ end
396
+
397
+ def degree(v)
398
+ find_edges(v).length
399
+ end
400
+
401
+ def pop(*vs)
402
+ # pop assumes vertices come in as their name, not a full vertex object
403
+ k1hood = self[*vs] # get k-1 N
404
+ vs.each { |v| @v.delete v } # delete k-1 N vertices
405
+ k1hood.e.keys.each { |e| @e.delete e } # delete k-1 N edges
406
+ k1hood # return k-1 N
407
+ end
408
+
409
+ def span(plan)
410
+
411
+ remaining = dup
412
+ popped = remaining.pop plan.get_root(remaining)
413
+ tree = Graph.new << popped.root
414
+
415
+ until remaining.v.empty?
416
+ border = self[*tree.v.keys][*remaining.v.keys] # find edges on the border between the tree and the remaining graph
417
+ edge = plan.get_edge(border) # choose one edge and new node to add to the tree
418
+ tree << edge # add to the tree
419
+ remaining.pop(edge.from, edge.to) # remove k-1 N from remaining graph
420
+ end
421
+
422
+ tree
423
+ end
424
+
425
+ def energy
426
+ @v.values.inject(0.0) { |sum,v| sum += (v.p.v * v.p.m).norm }
427
+ end
428
+ end
429
+
430
+ # edge/node selection plan for things like spanning trees
431
+ class Plan
432
+ end
433
+
434
+ class FirstPlan < Plan
435
+ def self.get_edge(graph)
436
+ graph.e.values.first
437
+ end
438
+ def self.get_root(graph)
439
+ graph.v.keys.first
440
+ end
441
+ end
442
+
443
+ # layouts take graphs and output canvases, when then can be printed or whatever
444
+
445
+ class Layout
446
+ def self.[](*as)
447
+ self.layout(*as)
448
+ self
449
+ end
450
+ def self.layout(*as)
451
+ raise 'undefined method'
452
+ end
453
+ end
454
+
455
+ # random layout places vertices at random and then connects them via L1-norm edges
456
+
457
+ class RandomLayout < Layout
458
+ def self.layout(g,width,height)
459
+ g.v.values.each { |v| v.p.p.randomize!(width,height) }
460
+ self
461
+ end
462
+ end
463
+
464
+ class SmartLayout < Layout
465
+
466
+ CW_SZ = 200 # convolution window size
467
+
468
+ def self.layout(g,iter=nil)
469
+
470
+ RandomLayout[g, 40, 24] if iter.nil?
471
+
472
+ es = []
473
+ ps = g.v.values.map { |v| v.p }
474
+ ses = 0
475
+
476
+ while iter.nil? or (iter -= 1) >= 0;
477
+
478
+ # update the gravity field
479
+ ps.each { |p| p.a = p.n_body_acceleration(ps.reject { |q| q.object_id == p.object_id }) }
480
+
481
+ # update the bond field
482
+ g.v.keys.each do |v|
483
+ p = g.v[v].p
484
+ qs = g.neighborhood v
485
+ p.b = p.n_body_bond(qs.map { |q| q.p })
486
+ end
487
+
488
+ # update the velocities
489
+ ps.each do |p|
490
+ p.v *= Particle::Z
491
+ p.v += p.a * Particle::DT
492
+ p.v += p.b * Particle::DT
493
+ end
494
+
495
+ # project new positions
496
+ ps.each { |p| p.p += p.v * Particle::DT }
497
+
498
+ # check system energy delta or iteration count
499
+
500
+ es << g.energy
501
+
502
+ if es.length >= CW_SZ
503
+ es = es[-CW_SZ,CW_SZ]
504
+ pses = ses
505
+ ses = es[-CW_SZ,CW_SZ].inject(0.0) { |sum,e| sum += e }.to_f / CW_SZ
506
+ de = ses - pses
507
+ break if de.abs < 0.005
508
+ else
509
+ ses = 1000
510
+ de = 1000
511
+ end
512
+ end
513
+
514
+ self
515
+ end
516
+ end
517
+
518
+ end # module ChatChart
data/lib/particle.rb ADDED
@@ -0,0 +1,93 @@
1
+ #
2
+ # particle.rb - Physics Particle Code
3
+ #
4
+ # Copyright(c) 2009 by Christopher Abad
5
+ # aempirei@gmail.com
6
+ # http://www.twentygoto10.com/
7
+ #
8
+ # this code is licensed under the "don't be a retarded asshole" license.
9
+ # if i don't like how you use this software i can tell you to fuck off
10
+ # and you can't use it, otherwise you can use it.
11
+ #
12
+
13
+ require 'point'
14
+
15
+ module ChatChart
16
+ class Particle
17
+
18
+ DT = 0.50 # time delta
19
+ G = -20.0 # universal constant of gravity
20
+ M = 300.0 # electron charge bond constant
21
+ Z = 0.90 # friction
22
+ D = 8.00 # bond distance
23
+
24
+ EPSILON = 0.03
25
+ NORMMIN = 0.002
26
+
27
+ attr_accessor :p # position
28
+ attr_accessor :m # mass
29
+ attr_accessor :v # velocity
30
+ attr_accessor :a # acceleration
31
+ attr_accessor :b # bond
32
+
33
+ def hash
34
+ object_id.hash
35
+ end
36
+
37
+ def self.[](*ns)
38
+ self.new P[*ns].vector
39
+ end
40
+
41
+ def initialize(p=P[0.0,0.0])
42
+ @m = 1.0
43
+ @v = P[0.0,0.0]
44
+ @p = p
45
+ end
46
+
47
+ def acceleration(p)
48
+ force(p) / @m
49
+ end
50
+
51
+ # electron bond attraction
52
+ def bond(p)
53
+ v = p.p - self.p
54
+ return P.zero if v == P.zero
55
+ return P.zero if v.aspect.norm < D - EPSILON
56
+ v0 = v.aspect.norm + D
57
+ v.unit * M / (v0 * v0)
58
+ end
59
+
60
+ def n_body_acceleration(ps)
61
+ v = ps.inject(P[].vector) { |acc,p| acc += acceleration p }
62
+ v.aspect.norm < NORMMIN ? P.zero : v
63
+ end
64
+
65
+ def n_body_bond(ps)
66
+ v = ps.inject(P[].vector) { |acc,p| acc += bond p }
67
+ v.aspect.norm < NORMMIN ? P.zero : v
68
+ end
69
+
70
+ # monopole short acting repulsive nuclear force
71
+ def force(p)
72
+ v = p.p - self.p
73
+ return P.zero if v == P.zero
74
+ return P.zero if v.aspect.norm > D + EPSILON
75
+ v0 = v.aspect.norm + D
76
+ v.unit * G * @m * p.m / (v0 * v0)
77
+ end
78
+ end
79
+
80
+ class GParticle < Particle
81
+ DT = 0.5 # time delta
82
+ G = 50.0 # universal constant of gravity
83
+ Z = 0.98 # friction
84
+ D = 3.00 # bond distance
85
+ # monopole short acting repulsive nuclear force
86
+ def force(p)
87
+ v = p.p - self.p
88
+ return P.zero if v == P.zero
89
+ v0 = v.aspect.norm + D
90
+ v.unit * G * @m * p.m / (v0 * v0)
91
+ end
92
+ end
93
+ end
data/lib/point.rb ADDED
@@ -0,0 +1,167 @@
1
+ #
2
+ # Point
3
+ # point.rb - Simple 2-D Point
4
+ #
5
+ # Copyright(c) 2009 by Christopher Abad
6
+ # aempirei@gmail.com
7
+ # http://www.twentygoto10.com/
8
+ #
9
+ # this code is licensed under the "don't be a retarded asshole" license.
10
+ # if i don't like how you use this software i can tell you to fuck off
11
+ # and you can't use it, otherwise you can use it.
12
+ #
13
+
14
+ module ChatChart
15
+ class P
16
+ attr_accessor :x
17
+ attr_accessor :y
18
+
19
+ def initialize(*ns)
20
+ @x = ns[-2] || 0
21
+ @y = ns[-1] || 0
22
+ end
23
+
24
+ def self.[](*ns)
25
+ new *ns
26
+ end
27
+
28
+ I = P[1,0]
29
+ J = P[0,1]
30
+
31
+ def self.zero
32
+ return P[]
33
+ end
34
+
35
+ def randomize!(width,height)
36
+ @x = rand width
37
+ @y = rand height
38
+ self
39
+ end
40
+
41
+ def self.random(width,height)
42
+ new.randomize! width, height
43
+ end
44
+
45
+ def quantize!
46
+ self << self.quantize
47
+ end
48
+
49
+ def quantize
50
+ P[@x.round.to_i, @y.round.to_i]
51
+ end
52
+
53
+ def vector
54
+ P[@x.to_f,@y.to_f]
55
+ end
56
+
57
+ def to_s
58
+ "(%05.1f,%05.1f)" % [@x,@y]
59
+ end
60
+
61
+ def to_a
62
+ [@x,@y]
63
+ end
64
+
65
+ def eql?(p)
66
+ p.is_a?(P) and (@x == p.x) and (@y == p.y)
67
+ end
68
+
69
+ def hash
70
+ [@x,@y].hash
71
+ end
72
+
73
+ def <=>(p)
74
+ (@x * @x + @y * @y) <=> (p.x * p.x + p.y * p.y)
75
+ end
76
+
77
+ # vector addition
78
+ def +(p)
79
+ P[@x + p.x, @y + p.y]
80
+ end
81
+
82
+ # both scalar product and dot product
83
+ def *(v)
84
+ case v
85
+ when P
86
+ (@x * v.x + @y * v.y)
87
+ else
88
+ P[@x * v, @y * v]
89
+ end
90
+ end
91
+
92
+ def /(v)
93
+ self * (1.0 / v.to_f)
94
+ end
95
+
96
+ def unit
97
+ return P.zero if self == P.zero
98
+ self / norm
99
+ end
100
+
101
+ def comp(b)
102
+ norm * cos(b)
103
+ end
104
+
105
+ def monic
106
+ self.vector / @x
107
+ end
108
+
109
+ def proj(b)
110
+ b.unit * (self * b.unit)
111
+ end
112
+
113
+ def cos(b)
114
+ unit * b.unit
115
+ end
116
+
117
+ S2 = Math::sqrt(2.0)
118
+
119
+ def aspect(k=1.3)
120
+ P[@x, @y * k]
121
+ end
122
+
123
+ def norm(v=2)
124
+ case v
125
+ when 1
126
+ (@x.abs + @y.abs).to_f
127
+ when 2
128
+ Math::hypot @x, @y
129
+ when Numeric
130
+ (@x.abs ** v + @y.abs ** v) ** (1.0 / v)
131
+ when :ascii
132
+ aspect.norm
133
+ when :min
134
+ [@x.abs,@y.abs].min
135
+ when :max
136
+ [@x.abs,@y.abs].max
137
+ when :h
138
+ norm(:min) * (S2 - 1) + norm(:max)
139
+ else
140
+ 1.0
141
+ end
142
+ end
143
+
144
+ def <<(p)
145
+ @x = p.x
146
+ @y = p.y
147
+ self
148
+ end
149
+
150
+ def u! ; self << self.u ; end
151
+ def d! ; self << self.d ; end
152
+ def l! ; self << self.l ; end
153
+ def r! ; self << self.r ; end
154
+
155
+ def u ; P[@x,@y-1] ; end
156
+ def d ; P[@x,@y+1] ; end
157
+ def l ; P[@x-1,@y] ; end
158
+ def r ; P[@x+1,@y] ; end
159
+
160
+ def ==(p) ; eql?(p) ; end
161
+ def %(p) ; P[@x,p.y] ; end
162
+ def -@ ; P[-@x,-@y] ; end
163
+ def +@ ; self ; end
164
+ def ~@ ; P[@y,@x] ; end
165
+ def -(p) ; self + -p ; end
166
+ end
167
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chatchart
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Christopher Abad
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-22 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: This is a package of various crappy ruby code which generally revolves around ASCII drawing and graph layout.
17
+ email: aempirei@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - demos/demo-animated.rb
26
+ - demos/demo-simple.rb
27
+ - demos/demo-physics.rb
28
+ - demos/demo-automatic.rb
29
+ - lib/chatchart.rb
30
+ - lib/particle.rb
31
+ - lib/point.rb
32
+ - README
33
+ - LICENSE
34
+ has_rdoc: true
35
+ homepage: http://www.twentygoto10.com/
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.3.5
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: ASCII drawing and graph layout
62
+ test_files: []
63
+