RubyPloticus


原文: RubyPloticus    ruby        2006年6月19日            Bliki 索引

译注:代码和生成的图片示例可从这里下载。

在最近的帖子“ 评估Ruby”中,我提到一位同事曾在一个Web应用中加入了一些漂亮的数据图表,有人email问我是怎样实现的,我在原来那篇帖子上添了句简短的回答:用Ploticus。这就带来另一个问题——他是怎样把Ruby和Ploticus连起来的呢?

最近我自己也遇到个类似的问题,要用Ploticus把一个个人项目的一些数据图表化。我的解决办法虽然远不如那位同事的那么精致,但实际上很相似。于是我觉得应该和大家分享一下。

首先我声明一条警告——这只不过是我花了一个晚上弄出来的东西,并没想做得很鲁棒,也没怎么考虑性能,更别说“企业级超复杂”了——只是我自己、我一个人用来处理一些数据的。

要想整合并驱动一个Ploticus之类的C库,一种复杂而完善的办法是直接绑定C API,虽然别人告诉我用Ruby做这件事也很简单,但它的工作量对我来说还是太多了(尤其是我想在鸡尾酒时间之前搞定它:-)),因此我的做法是构建一份Ploticus脚本,通过管道(pipe)输给Ploticus。来自标准输入的脚本可以控制Ploticus做事,于是我要做的只是在Ruby中运行Ploticus,把脚本命令通过管道传给它。大致如下:

  def generate script, outfile
    IO.popen("ploticus -png -o #{outfile} -stdin", 'w'){|p| p << script}
  end

为了构建脚本,我想让Object们按我规定的条款工作,生成所需的Ploticus懂的东西。如果你在什么地方用到了Ploticus的预制件(prefabs),搭建东西就轻而易举了。我要画一张簇状条线图,就像 这种,这需要一份Ploticus脚本。

我把要做的东西分三层构建,最底层是PloticusScripter,用这个class生成Ploticus脚本命令,如下所示:

class PloticusScripter
  def initialize
    @procs = []
  end
  def proc name
    result =  PloticusProc.new name
    yield result
    @procs << result
    return result
  end
  def script
    result = ""
    @procs.each do |p|
      result << p.script_output << "/n/n"
    end
    return result   
  end
end
class PloticusProc
  def initialize name
    @name = name
    @lines = []
  end
  def script_output
    return (["#proc " + @name] + @lines).join("/n")
  end
  def method_missing name, *args, &proc
    line = name.to_s + ": "
    line.tr!('_', '.')
    args.each {|a| line << a.to_s << " "}
    @lines << line
  end
end

可以看到,一个PloticusScripter对象有一个实例变量@procs,是个存proc命令的链表(所谓proc命令,就是能响应 script_output方法调用的东西——没有其他要求)。我可以实例化一个PloticusScripter,反复调用它的proc方法来定义我需要的proc命令加到链表尾,完成之后调用script方法获得要用管道输给Ploticus的整个脚本。

往上一层用来构建簇状条线图:

class PloticusClusterBar
  attr_accessor :rows, :column_names
  def initialize
    @rows = []
  end
  def add_row label, data
    @rows << [label] + data
  end
  def getdata scripter
    scripter.proc("getdata") do |p|
      p.data generate_data
    end
  end
  def colors
    %w[red yellow blue green  orange]
  end
  def clusters scripter
    column_names.size.times do |i|
      scripter.proc("bars") do |p|
        p.lenfield i + 2
        p.cluster i+1 , "/", column_names.size
        p.color colors[i]
        p.hidezerobars 'yes'
        p.horizontalbars 'yes'
        p.legendlabel column_names[i]
      end   
    end
  end
 
  def generate_data
    result = []
    rows.each {|r| result << r.join(" ")}
    result << "/n"
    return result.join("/n")   
  end 
end

有了PloticusClusterBar,我就能调用它的add_row方法添加数据行构建图表了,为图表增加数据变得非常简单。

为了画一张特定的图,还要在前两层之上再写一个class:

#生成的图与ploticus/gallery/students.htm里的例子类似

class StudentGrapher
  def initialize
    @ps = PloticusScripter.new
    @pcb = PloticusClusterBar.new
  end
  def run
    load_data
    @pcb.getdata @ps
    areadef
    @pcb.clusters @ps   
  end
  def load_data
    @pcb.column_names = ['Exam A', 'Exam B', 'Exam C', 'Exam D']
    @pcb.add_row '01001', [44, 45, 71, 89]
    @pcb.add_row '01002', [56, 44, 54, 36]
    @pcb.add_row '01003', [46, 63, 28, 87]
    @pcb.add_row '01004', [42, 28, 39, 49]
    @pcb.add_row '01005', [52, 74, 84, 66]   
  end
  def areadef
    @ps.proc("areadef") do |p|
      p.title "Example Student Data"
      p.yrange 0, 6
      p.xrange 0, 100
      p.xaxis_stubs "inc 10"
      p.yaxis_stubs "datafield=1"
      p.rectangle 1, 1, 6, 6
    end
  end
  def generate outfile
    IO.popen("ploticus -png -o #{outfile} -stdin", 'w'){|p| p << script}
  end
  def script
    return @ps.script
  end
 
end
 
 
def run
  output = 'fooStudents.png'
  File.delete output if File.exists? output
  s = StudentGrapher.new
  s.run
  s.generate output
end

上面这个例子非常简单,但它很好地展示了一个模式——我称之为 Gateway模式。PloticusClusterBar class是一个gateway,它的接口正好适合我要做的事,通过它便利的接口转换出实际输出需要的东西。PloticusScripter class是另一层gateway。即便做这么一件简单的事,我仍然觉得这样编排一组object是个不错的设计——或许这只能说明这些年来我的头脑扭曲成啥模样了
python+opencv简谱识别音频生成系统源码含GUI界面+详细运行教程+数据 一、项目简介 提取简谱中的音乐信息,依据识别到的信息生成midi文件。 Extract music information from musical scores and generate a midi file according to it. 二、项目运行环境 python=3.11.1 第三方库依赖 opencv-python=4.7.0.68 numpy=1.24.1 可以使用命令 pip install -r requirements.txt 来安装所需的第三方库。 三、项目运行步骤 3.1 命令行运行 运行main.py。 输入简谱路径:支持图片或文件夹,相对路径或绝对路径都可以。 输入简谱主音:它通常在第一页的左上角“1=”之后。 输入简谱速度:即每分钟拍数,同在左上角。 选择是否输出程序中间提示信息:请输入Y或N(不区分大小写,下同)。 选择匹配精度:请输入L或M或H,对应低/中/高精度,一般而言输入L即可。 选择使用的线程数:一般与CPU核数相同即可。虽然python的线程不是真正的多线程,但仍能起到加速作用。 估算字符上下间距:这与简谱中符号的密集程度有关,一般来说纵向符号越稀疏,这个值需要设置得越大,范围通常在1.0-2.5。 二值化算法:使用全局阈值则跳过该选项即可,或者也可输入OTSU、采用大津二值化算法。 设置全局阈值:如果上面选择全局阈值则需要手动设置全局阈值,对于.\test.txt中所提样例,使用全局阈值并在后面设置为160即可。 手动调整中间结果:若输入Y/y,则在识别简谱后会暂停代码,并生成一份txt文件,在其中展示识别结果,此时用户可以通过修改这份txt文件来更正识别结果。 如果选择文件夹的话,还可以选择所选文件夹中不需要识别的文件以排除干扰
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值