Maximum Distance Bisector
about 500 lines of tk codes for Bisector CRUD
#!/usr/bin/env ruby require 'tk' PntRad = 3 # Point radius of TkcOval ScrHgt = 600 # Screen Hight class Geom Infinity = 10*ScrHgt @@canvas = Hash.new attr_accessor :g def initialize @g = nil end def delete if @g.class <= Array @g.each {|g| delete_geom g} else @g.destroy end end def has? g @g === g end def []=(*args) if @g.class <= Array @g.each {|g| g.[]= *args} else @g.[]= *args end end def [](arg) if @g.class <= Array @g.each {|g| g.[] arg} else @g.[] arg end end def Geom.[]=(key, val) @@canvas[key] = val end def Geom.[](key) @@canvas[key] end def Geom.size @@canvas.size end end class Point < Geom attr_accessor :x, :y, :coords, :size def initialize *args super() if args.length > 1 && args[0].kind_of?(Fixnum) && args[1].kind_of?(Fixnum) @x, @y = args.shift, args.shift elsif args.length > 0 && args[0].kind_of?(Array) && !args[0].empty? pnt = args.shift @x = pnt[0] @y = pnt[1] else @x, @y = Infinity, Infinity end # default style args << {:fill=>nil} unless args.find {|e| e.class <= Hash && e.has_key?(:fill)} args << {:outline=>'black'} unless args.find {|e| e.class <= Hash && e.has_key?(:outline)} h = args.find {|e| e.class <= Hash && e.has_key?(:size)} if h @size = h[:size] else args << {:size=>PntRad} @size = PntRad end @g = add_point @x, @y, *args Geom[@g] = self end def set *args if args.length > 1 && args[0].kind_of?(Fixnum) && args[1].kind_of?(Fixnum) @x, @y = args.shift, args.shift elsif args.length > 0 && args[0].kind_of?(Array) && !args[0].empty? pnt = args.shift @x = pnt[0] @y = pnt[1] else @x, @y = Infinity, Infinity end set_point @g, @x, @y, @size, *args end def coords= p @x, @y = p set_point @g, @x, @y, @size end def coords [@x, @y] end def size= rad @size = rad set_point @g, @x, @y, @size end def size @size end def move x, y @g.move x, -y @x += x @y += y end end class Line < Geom attr_accessor :p0, :p1, :coords def initialize *args super() if !args.empty? && args[0].class <= Array && args[0][0].kind_of?(Fixnum) && args[0][1].kind_of?(Fixnum) @p0 = args.shift elsif !args.empty? && args[0].class <= Point @p0 = args.shift.coords else @p0 = [0, 0] end if !args.empty? && args[0].class <= Array && args[0][0].kind_of?(Fixnum) && args[0][1].kind_of?(Fixnum) @p1 = args.shift elsif !args.empty? && args[0].class <= Point @p1 = args.shift.coords else @p1 = [0, 0] end @g = add_line @p0[0], @p0[1], @p1[0], @p1[1], *args Geom[@g] = self end def set x0, y0, x1, y1, *args @p0 = [x0, y0] @p1 = [x1, y1] set_line @g, x0, y0, x1, y1 *args end def coords= args @p0, @p1 = args set_line @g, @p0[0], @p0[1], @p1[0], @p1[1] end def coords [@p0, @p1] end def move x, y @g.move x, -y @p0[0] += x @p0[1] += y @p1[0] += x @p1[1] += y end end def delete_geom(g) if g.class <= Array g.each {|e| delete_geom e} else g.destroy end end def add_point(x, y, *rest) pnt = TkcOval.new($canvas, 0, 0, 0, 0) rest << {:fill=>'red'} unless rest.find do |e| e.class <= Hash && e.has_key?(:fill) end if rad = rest.find {|e| e.class <= Hash && e.has_key?(:size)} r = rad[:size] else r = PntRad end set_point(pnt, x, y, r, *rest) end def set_point(pnt, x, y, r, *rest) pnt.coords = x-r, ScrHgt-y-r, x+r, ScrHgt-y+r rest.each {|h| h.each {|k,v| pnt[k]=v unless k == :size}} pnt end def add_line(x0, y0, x1, y1, *rest) line = TkcLine.new($canvas, 0, 0, 0, 0) rest << {:fill=>'blue'} unless rest.find do |e| e.class <= Hash && e.has_key?(:fill) end set_line(line, x0, y0, x1, y1, *rest) end def set_line(line, x0, y0, x1, y1, *rest) line.coords = x0, ScrHgt-y0, x1, ScrHgt-y1 rest.each {|h| h.each {|k,v| line[k]=v}} line end def add_text(x, y, msg, *rest) text = TkcText.new($canvas, 0, 0, *rest) rest << {:fill=>'black'} unless rest.find do |e| e.class <= Hash && e.has_key?(:fill) end rest << {:font=>'Courier -24 bold'} unless rest.find do |e| e.class <= Hash && e.has_key?(:font) end set_text(text, x, y, msg, *rest) end def set_text(text, x, y, msg, *rest) text.coords x, ScrHgt - y text[:text] = msg rest.each{|h| h.each{|k,v| text[k]=v}} text end def find_geoms tag $canvas.find_withtag(tag).map{|e| Geom[e]} end def lincomb base, vec, len [base[0] + vec[0]*len, base[1] + vec[1]*len] end def dist p0, p1 [(p0.x - p1.x).abs, (p0.y - p1.y).abs].max end class Bisector < Geom Len = ScrHgt attr_accessor :p0, :p1, :bd, :d0, :d1, :dd, :q0, :q1, :bs, :w0, :w1 def initialize p0, p1, *args # default style args << {:tags=>$bsectag} unless args.find {|e| e.class <= Hash && e.has_key?(:tags)} args << {:fill=>'gray75'} unless args.find {|e| e.class <= Hash && e.has_key?(:fill)} @bs = Line.new *args args.delete_if {|e| e.has_key?(:tags)} @w0 = Line.new *args @w1 = Line.new *args set p0, p1, *args @g = [@bs, @w0, @w1].map! {|e| Geom[e.g] = self; e.g} end def set p0, p1, *args @p0 = p0 @p1 = p1 bisect end def delete @bs.delete @w0.delete @w1.delete super end def bisect wingbases @p0, @p1 @bs.coords = @q0, @q1 wingdirs @p0.x-@p1.x, @p0.y-@p1.y @w0.coords = @q0, lincomb(@q0, @d0, Len) @w1.coords = @q1, lincomb(@q1, @d1, Len) end def wingbases p0, p1 xmax = [p0.x, p1.x].max xmin = [p0.x, p1.x].min xdif = (p0.x - p1.x).abs xmid = (p0.x + p1.x)/2 ymax = [p0.y, p1.y].max ymin = [p0.y, p1.y].min ydif = (p0.y - p1.y).abs ymid = (p0.y + p1.y)/2 if xdif < ydif @q0 = [xmin + ydif/2, ymid] @q1 = [xmax - ydif/2, ymid] @dd = [1, 0] elsif xdif > ydif @q0 = [xmid, ymin + xdif/2] @q1 = [xmid, ymax - xdif/2] @dd = [0, 1] else #xdif == ydif @q0 = @q1 = [xmid, ymid] @dd = [0, 0] end end def wingdirs dx, dy if dx == 0 @d0, @d1 = [1, 0], [-1, 0] elsif dy == 0 @d0, @d1 = [0, 1], [0, -1] elsif dx*dy < 0 # i.e. near -45 deg @d0, @d1 = [1, 1], [-1, -1] elsif dx.abs < dy.abs # i.e. near +45 deg and steep @d0, @d1 = [1, -1], [-1, 1] else # dx.abs >= dy.abs i.e. near +45 deg and flat @d0, @d1 = [-1, 1], [1, -1] end if @dd[0] == 0 and @dd[1] == 0 @dd = @d0 end end def belong_to? p p === @p0 || p === @p1 end def distance dist @p0, @p1 end end def reset_bisector s0, s1, *args find_geoms($bsectag).map{|b| b.delete if b.belong_to?(s0) && b.belong_to?(s1) } Bisector.new s0, s1, *args end if __FILE__ == $0 def init *args root = TkRoot.new *args $canvas = TkCanvas.new(root) { width ScrHgt height ScrHgt borderwidth 1 relief 'sunken' } $canvas.pack $seltag = TkcTag.new($canvas) $status = add_text 250, 20, "Mouse buttons: Left=>Drag, Middle=>Delete, Right=>Add\n", :font=>'Courier -12' end def run msgopt = { :icon=>'warning', :message=>'Realy quit?', :type=>'okcancel', :default=>'ok', :title=>'WARNING' } TkButton.new(:text=>'Quit') {|b| command proc{ exit if Tk.messageBox(msgopt.merge(:parent=>b)) == 'ok'} pack :expand=>false, :anchor=>:e } Tk.mainloop end def scrumble_bsec period = 10 # ms ntimes = 200 bsecs = find_geoms $bsectag sites = find_geoms $sitetag n = sites.length i = 0 TkAfter.new(period, ntimes, proc{ sites.each_with_index {|p, j| if j == rand(n) # or j == i%n a = rand 20 b = rand 20 case rand 100 when 1..80 a *= Math.sin(i%11? i/29: -i/31) b *= Math.cos(i%13? i/47: -i/43) when 81..90 p.move b, -a else p.move -a, b end bsecs.each {|b| if b.belong_to? p then b.bisect end} end i += 1 } } ).start end def bind_bsec all = TkcTagAll.new($canvas) cur = TkcTagCurrent.new($canvas) mark = [0, 0] # canvas coordinates (upside down) target = nil # a selected Geom object asite = nil # a site that has the selected 'target' bsecs = nil # surrounding bisectors that belong to the site all.bind 'ButtonPress-1', proc{|x,y| if cur.find[0].class <= TkcOval target = cur.find[0] mark = [x, y] asite = find_geoms($sitetag).find{|p| p.has? target} bsecs = find_geoms($bsectag).select{|b| b.belong_to? asite} end }, '%x %y' all.bind 'Button1-Motion', proc {|x,y| if target asite.move x-mark[0], mark[1]-y bsecs.each {|b| b.bisect} unless bsecs.empty? mark = [x, y] end }, '%x %y' all.bind 'ButtonRelease-1', proc{target = nil} all.bind 'ButtonPress-2', proc{ item = Geom[cur.find.first] if item == nil or item.class <= Bisector Tk.messageBox({ :icon=>'warning', :message=>'Can\'t delete bisectors', :type=>'okcancel', :default=>'ok', :title=>'WARNING' }) else item.delete show_stat end } $canvas.bind 'ButtonPress-3', proc{|x,y| add_site x, ScrHgt-y}, '%x %y' TkButton.new(:text=>'Scrumble') {|b| command proc{ yield } pack :expand=>false, :anchor=>:center } end def color r, g, b colors = ['00', '33', '66', '99', 'cc', 'ff'] c = '#' + colors[r] + colors[g] + colors[b] end def random_color color rand(5), rand(5), rand(5) end def add_site x, y sites = find_geoms $sitetag p = Point.new x, y, :tags=>$sitetag, :outline=>'black', :size=>6 p.g[:fill] = random_color sites.map{|q| reset_bisector p, q, :fill=>'blue'} show_stat p end def show_stat bsecs = find_geoms $bsectag sites = find_geoms $sitetag $status[:text] = "Mouse buttons: Left=>Drag, Middle=>Delete, Right=>Add\n" + "# of sites: #{sites.size}, # of bisectors #{bsecs.size}" end class Point def delete find_geoms($bsectag).map{|b| b.delete if b.belong_to? self} super end end init 'title'=>"Bisectors" $sitetag = TkcTag.new($canvas) $bsectag = TkcTag.new($canvas) s0 = add_site 200, 200 s1 = add_site 400, 250 s2 = add_site 350, 450 bind_bsec { scrumble_bsec } run end