chatchart 1.0.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.
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
+