# left drag: expand # right drag: shrink # both drag: move # double click: autoscale class TkGraph include Tk def initialize(xl=300,yl=300,w=nil,side="top") Tk.root.cursor "arrow" @c=TkCanvas.new(w, 'width'=>xl, 'height'=>yl, 'borderwidth'=>1, 'relief'=>'sunken').pack('side'=>side) @c.bind('Double-Button-1',proc{|x,y| @auto=(not @auto);autoscale()},'%x %y') @c.bind('1',proc{|x,y| box_start(x,y); Tk.root.cursor "sizing"},'%x %y') @c.bind('B1-Motion',proc{|x,y| box_resize(x,y)},'%x %y') @c.bind('ButtonRelease-1',proc{|x,y| enlarge(x,y); Tk.root.cursor "arrow"},'%x %y') @c.bind('2',proc{|x,y| move_start(x,y); Tk.root.cursor "fleur"},'%x %y') @c.bind('B2-Motion',proc{|x,y| move(x,y);},'%x %y') @c.bind('ButtonRelease-2',proc{|x,y| redraw();Tk.root.cursor "arrow"},'%x %y') @c.bind('3',proc{|x,y| box_start(x,y); Tk.root.cursor "draped_box"},'%x %y') @c.bind('B3-Motion',proc{|x,y| box_resize(x,y)},'%x %y') @c.bind('ButtonRelease-3',proc{|x,y| shrink(x,y); Tk.root.cursor "arrow"},'%x %y') @c.bind('Double-Button-3',proc{|x,y| set_style(x,y)},'%x %y') @all=TkcTagAll.new(@c) @wave=[] @x,@y=nil,nil @xl,@yl=xl,yl #width,height @x0,@y0=0,0 #origin @xs,@ys=1,1 #scale @xb,@yb=nil,nil #box @xc0,@yc0,@xc1,@yc1=nil,nil,nil,nil #coordination @xstyle="%.2e";@ystyle="%.2e" @box=nil @mark=false @auto=true redraw() end attr_reader :x0 attr_reader :y0 attr_reader :xs attr_reader :ys attr_accessor :mark attr_accessor :auto attr_accessor :xstyle attr_accessor :ystyle def scale(x,y) return [ ((x.to_f-@x0)/@xs*@xl).to_i, @yl-((y.to_f-@y0)/@ys*@yl).to_i, ] end def unscale(x,y) return [ x.to_f/@xl*@xs+@x0, (@yl-y).to_f/@yl*@ys+@y0, ] end def axis() x,y=scale(0,0) @xc0.addtag("axis") if @xc0 @xc1.addtag("axis") if @xc1 @yc0.addtag("axis") if @yc0 @yc1.addtag("axis") if @yc1 TkcLine.new(@c,x,-@yl,x,@yl*2,'fill'=>'red') TkcLine.new(@c,-@xl,y,@xl*2,y,'fill'=>'red') @xc0=TkcText.new(@c,10,@yl-10,'text'=>sprintf(@xstyle,@x0),'anchor'=>'w') @xc1=TkcText.new(@c,@xl,@yl-10,'text'=>sprintf(@xstyle,@x0+@xs),'anchor'=>'e') @yc0=TkcText.new(@c,1,@yl-20,'text'=>sprintf(@ystyle,@y0),'anchor'=>'w') @yc1=TkcText.new(@c,1,10,'text'=>sprintf(@ystyle,@y0+@ys),'anchor'=>'w') @c.delete("axis") @c.dtag("axis") end def point(x,y,x0=@x,y0=@y) x1,y1=scale(x,y) x01,y01=scale(x0,y0) TkcLine.new(@c,x01,y01,x1,y1) unless @x==nil TkcOval.new(@c,x1-1,y1-1,x1+1,y1+1,'fill'=>'black','outline'=>'black') if @mark @x,@y=x,y end def redraw() x,y=@x,@y @x,@y=nil,nil # @wave.each{|p| point(*p)} w=@wave.collect{|p| scale(*p)} @c.addtag_all("old") axis() TkcLine.new(@c,*w.flatten) if @wave.size>1 for p in w TkcOval.new(@c,p[0]-1,p[1]-1,p[0]+1,p[1]+1,'fill'=>'black','outline'=>'black') end if @mark @c.delete("old") @c.dtag("old") @x,@y=x,y end def box_start(x,y) @xb,@yb=x,y @box=TkcRectangle.new(@c,x,y,@xb,@yb,'width'=>1) end def box_resize(x,y) @box.coords(@xb,@yb,x,y) end def enlarge(x,y,x0=@xb,y0=@yb) @box.destroy if (x-x0).abs>@xl/20 and (y-y0).abs>@yl/20 x1,y1=unscale(x0,y0) x2,y2=unscale(x,y) @xs=(x1-x2).abs @ys=(y1-y2).abs @x0=[x1,x2].sort[0] @y0=[y1,y2].sort[0] redraw() end end def shrink(x,y,x0=@xb,y0=@yb) @box.destroy if (x-x0).abs>@xl/20 and (y-y0).abs>@yl/20 x1,y1=unscale(x0,y0) x2,y2=unscale(x,y) @xs=@xs*2-(x1-x2).abs @ys=@ys*2-(y1-y2).abs @x0=@x0*2-[x1,x2].sort[0] @y0=@y0*2-[y1,y2].sort[0] redraw() end end def move_start(x,y) @xb,@yb=x,y end def move(x,y,x0=@xb,y0=@yb) @all.move(x-@xb,y-@yb) x1,y1=unscale(x0,y0) x2,y2=unscale(x,y) @x0-=x2-x1 @y0-=y2-y1 @xb,@yb=x,y axis() end def clear() @wave=[] @x,@y=nil,nil redraw() end def autoscale() xw=@wave.collect{|xy| xy[0]} yw=@wave.collect{|xy| xy[1]} xmin=xw.sort[0] xmax=xw.sort.last ymin=yw.sort[0] ymax=yw.sort.last @xs,@ys=(xmax-xmin)*1.1,(ymax-ymin)*1.1 @xs=1 if xmax==xmin @ys=1 if ymax==ymin @x0,@y0=(xmax+xmin-@xs)/2.0,(ymax+ymin-@ys)/2.0 redraw() end def add(x,y) @wave.push([x,y]) point(x,y) autoscale() if @auto end def shift(x,y) @wave.shift @wave.push([x,y]) if @auto autoscale() else redraw() end end def size() return @wave.size end def set_style(x=1,y=0) styles=["%d","%.2f","%.2e","%.3f","%.3e"] xy="x" xy="y" if x<(@yl-y) w=TkToplevel.new(nil,'title'=>xy+'-axis') st=nil TkFrame.new(w){|f| scr=TkScrollbar.new(f).pack('side'=>'right','fill'=>'y') st=TkListbox.new(f).pack('side'=>'right') st.yscrollbar(scr) for e in styles st.insert(styles.index(e),e) end }.pack TkButton.new(w,'text'=>'OK', 'command'=>proc{eval("@"+xy+"style=styles[st.curselection[0]]");w.destroy}).pack('side'=>'left') TkButton.new(w,'text'=>'cancel', 'command'=>proc{w.destroy}).pack('side'=>'right') end end #class