raubarede/Ruiby

View on GitHub
lib/ruiby_gtk/dsl/plot.rb

Summary

Maintainability
D
1 day
Test Coverage
# Creative Commons BY-SA :  Regis d'Aubarede <regis.aubarede@gmail.com>
# LGPL

module Ruiby_dsl
  #  define a plot zone, with several curves :
  #  pl=plot(400,200,{
  #      "curve1" => {
  #         data:[[0,1],[110,1],[20,1],[30,1],[10,1],[22,1],[55,1],[77,1]],
  #         color: '#FF0000', xminmax:[0,100], yminmax:[0,100], style: :linear,},...})
  #  }
  #
  # this methods are added :
  # * pl.set_data(name,data) : replace current values par a new list of point [ [y,x],....] for curve named 'name'
  # * pl.get_data(name) 
  # * pl.add_curve(name,{data:...}) : add a curve
  # * pl.delete_curve(name) : delete a curve  
  # * pl.add_data(name,pt)  : add a point at the end of the curve
  # * pl.scroll_data(name,value)  : add a point at last and scroll if necessary (act as oscilloscope)
  # see samples/plot.rb
  def plot(width,height,curves,config={})
     plot=canvas(width,height) do
       on_canvas_draw { |w,ctx| w.expose(w,ctx) }
       on_canvas_button_press { |w,event| w.track(event)}
     end
     def plot.config(c) @config=c end
     plot.config(config.merge({w: width,h:height}))
     def plot.add_curve(name,config) 
        c=config.dup
        c[:type] = :curve
        c[:data] ||= [[0,0],[100,100]]
        c[:maxlendata] ||= c[:data].size
        c[:color] ||= "#003030"
        c[:rgba] = ::Gdk::Color.parse(c[:color])
        c[:bgrgba] = c[:bgcolor] ? ::Gdk::Color.parse(c[:bgcolor]) : nil
        c[:xminmax] ||= [c[:data].first[1],c[:data].last[1]]
        c[:yminmax] ||= [0,100]
        c[:style] ||= :linear
        c[:xa] = 1.0*width_request/(c[:xminmax][1]-c[:xminmax][0])
        c[:xb] = 0.0    -c[:xminmax][0]*c[:xa]
        c[:ya] = 1.0*height_request/(c[:yminmax][0]-c[:yminmax][1])
        c[:yb] = 0.0-c[:yminmax][1]*c[:ya]
        @curves[name]=c
     end
     def plot.add_bar(name,values)
        c={type: :bar,data:values, xmin: values.first.first , xmax: values.last.first}
        @curves[name]=c
     end
     def plot.delete_curve(name) 
        @curves.delete(name)
        redraw
     end
     def plot.expose(w,ctx) 
        return unless @curves
        w.draw_rectangle(0,0,@config[:w],@config[:h],0,nil,@config[:bg],0) if @config[:bg]
        if @config[:grid] 
          dx=dy=(@config[:grid]||"40").to_i
          color=@config[:grid_color] || "#AAA"
          0.step(width_request,dx) {|x| 
            w.draw_line([x,0,x,height_request],color,1) 
          }
          0.step(height_request,dy) {|y|
            w.draw_line([0,y,width_request,y],color,1)           
          }
        end
        yb0=3
        @curves.values.each do |c|
              next if c[:data].size<2
              case c[:type] 
              when :curve
                  l=c[:data].each_with_object([]) { |(y,x),a|  
                    a << x*c[:xa]+c[:xb] ; a <<  y*c[:ya]+c[:yb] 
                  }
                  if c[:bgcolor]
                    l1= [0,c[:yb]] + l + [ c[:xminmax].last*c[:xa]+c[:xb] ,c[:yb]]
                    w.draw_polygon(l1,c[:bgcolor],c[:color],2)
                  else
                    w.draw_line(l,c[:color],2)
                  end
              when :bar
                 w.draw_varbarr(0,@config[:h]-yb0,@config[:w],@config[:h]-yb0,c[:xmin],c[:xmax],c[:data],5)
                 yb0+=5
              end
        end
        if @tx && @track_text
           w.draw_text_center(width_request/2,20,"Date: #{@track_title}",1.3,"#FFF","#000")
           w.draw_line([@tx,0,@tx,height_request],"#FFF",1)
           dx=(@tx<(width_request-70)) ? 10 : -10
           @track_text.each do |name,text,h,ht|
              w.draw_line([@tx,h,@tx+dx,ht],@curves[name][:color],1)
              if dx>0 
                w.draw_text(@tx+dx,ht,text,1,@curves[name][:color],"#000")
              else
                w.draw_text_left(@tx+dx,ht,text,1,@curves[name][:color],"#000")
              end
           end
        end
     end
     
     def plot.set_data(name,data) 
       @curves[name][:data]=data
       maxlen(name,@curves[name][:maxlendata])
       redraw
     end
     def plot.get_data(name) 
       @curves[name][:data]
     end
     def plot.add_data(name,pt) 
       @curves[name][:data] << pt
       maxlen(name,@curves[name][:maxlendata])
       redraw
     end
     def plot.scroll_data(name,value) 
        l=@curves[name][:data]
        pas=width_request/l.size
        l.each { |pt| pt[1]-=pas } 
        l << [ value , @curves[name][:xminmax].last ]
        maxlen(name,@curves[name][:maxlendata])
        redraw
     end
     def plot.maxlen(name,len)
       @curves[name][:data]=if @curves[name][:data].size>=len
        @curves[name][:data][-len..-1] 
       else
        @curves[name][:data]
       end
     end
     def plot.track(event)
      return unless @config[:tracker]
      x=nil
      lt=@curves.each_with_object([]) {|(name,d),a|
        next unless d[:type]==:curve
        x=(event.x-d[:xb])/d[:xa]
        y=psearch(d[:data],x)
        h=y*d[:ya]+d[:yb]
        a << [name,@config[:tracker][1].call(name,y) || "",h,(h>30) ? (h-10) : (h+10)]
      }
     50.times {
        t=false
        lt.each_with_index {|a,ia| h=a[3]
          ld=lt.each_with_index.select {|(n,t,h0,hh),ii| ii!=ia && (h-hh).abs<8}
          if ld.size>0
            ref=lt.each_with_object(0) {|(n,t,h0,hh),sum| sum+=(h-hh)}/ld.size
            moin=a[3]<20
            delta=(h-ref<0.1)? rand(h-2..h+2) : ((h-ref) > 0) ?  2 : -2 
            a[3]= [0,a[3]+delta,height_request].sort[1]
            t=true
            break 
          end
        }
        break unless t
      }
      @tx,@track_text,@track_title=event.x,lt,@config[:tracker][0].call(x)
     end
     def plot.psearch(lxy,x)
       imin,imax=0,(lxy.size-1)
       while imin<imax
          i= (imin+imax)/2
          vi=lxy[i][1]
          if vi<x
            imin=i+1
          elsif vi>x
            imax=i-1
          else
            return(lxy[i][0])
          end
       end
       return lxy[i][0]
     end

     def plot.init() @curves,@tx,@track_text={},nil,nil  end
     plot.init
     curves.each { |name,descr|  ; plot.add_curve(name,descr) }
     plot
  end
end