Charles Oliver Nutter

Java, Ruby, 和 JVM 爱好者,努力理解它们 Source About

使用 JRuby 和 JFreeChart 创建精美图表

我最近刚从 RubyKaigi 回来,在那里我有机会与日本 Ruby 社区的成员坐下来,向他们展示了一些 JRuby 的内容。 其中多次提到的一个问题是,从 Ruby 中利用外部库的难度:如果是 C 库,通常你必须编写一个 C 扩展,或者做额外的工作来编写一个 FFI 绑定。

如果该库不是用 C 或 Ruby 实现的,事情会变得更加奇怪。

一个例子是 Charty 库,它是 Ruby 上生成精美图表图形的更流行的选择之一。 但 Charty 实际上封装了一个 Python 库,名为 matplotlib,而 CRuby 的绑定实际上是将 Python 加载到当前进程中,并通过 Ruby C API 调用它,然后进行 Python C API 调用。 太可怕了!

最近,一个 Reddit Ruby 帖子 展示了使用 QuickChart 库,该库是用 JavaScript 实现的…… 呃! 在这种情况下,调用是通过进程启动到 QuickChart 命令行进行的,但无论哪种方式,它都没有真正集成到使用它的 Ruby 应用程序中。

有没有更好的方法? 当然有:使用 JRuby 中基于 JVM 的库! 这篇文章将向你展示如何开始使用 JRuby 的 Java 集成,以及一些从 Ruby 代码中使用 JFreeChart 库的快速示例。 我保证你不需要编写任何 Java, C, Python 或 JavaScript 代码。

JFreeChart

JFreeChart library 提供了广泛的图表格式,具有大量的渲染选项,并且易于与基于 JVM 的 GUI 工具包或简单的图像文件输出集成。 与许多其他图表库不同,它还支持实时更新给定的图表 GUI,使其成为桌面监控工具的绝佳选择。

使用 JRuby 构建 GUI 既有趣又容易,但在这篇文章中,我们将只关注 API 的图表和图像生成部分,以及如何从 Ruby 轻松使用它们。

JRuby 的 Java 集成

神奇之处始于 JRuby 的 Java integration layer。 使用 Java 库的最简单方法基本上是假装它是一个 Ruby 库,只需进行一些调整:

让我们从一个简单的例子开始。

调用 java.lang.Runtime 类

假设你已经安装了 JRuby 10,你可以直接从 IRB 开始使用 JVM 库。 让我们启动它,并确保我们在 JRuby 上运行。

$ irb
irb(main):001> RUBY_ENGINE
=> "jruby"
irb(main):002>

当然,JDK 带有大量内置功能,我喜欢使用 java.lang.Runtime 类进行快速演示。 它提供了查询有关运行 JVM 和主机操作系统的一些信息的方法。 让我们将其“导入”到我们的会话中,并调用一些方法。

irb(main):002> rt = java.lang.Runtime.runtime
=> #<Java::JavaLang::Runtime:0x39a87e72>
irb(main):003> rt.available_processors
=> 8
irb(main):004> rt.free_memory
=> 222145360

这里有一些神奇的地方,让它感觉真的很像 Ruby:

我们可以使用 JDK 中附带的库完成大量工作,从简单的数据库访问到专业的桌面 GUI 开发。

但是,如果我们想要使用的库未与 JDK 一起提供,我们需要获取它们。 始终可以直接下载库的 “jar” 文件,但你可能还需要找到它的依赖 jar。 更好的方法是使用 JRuby 的 jar-dependencies 库。

jar-dependencies

就像 Ruby gems 被推送到 rubygems.org 一样,Java 库以 jar 文件的形式发布到 Maven 仓库,这是一个全球性的、联合的仓库,包含人类已知的每个 Java 库的每个版本。 为了尽可能容易地从 JRuby 使用这些库,我们为 Ruby 和 Java 应用程序构建了广泛的 Maven 工具。

将 JVM 库从 Maven 加载到 JRuby 应用程序的最简单方法是使用 jar-dependencies,这是一种内置工具,用于在独立应用程序或 gems 中获取和管理 Java 依赖项。

对于我们的示例,我们要获取 JFreeChart 及其依赖库。 如果我们在 search.maven.org 上搜索它,我们可以获取其 “Maven 坐标”。 在 Maven Central 中搜索 JFreeChart 最新发布的工件是 jfreechart 1.5.5。 从这里我们可以看到我们正在寻找的坐标:

鉴于此,我们可以设置我们的 JFreeChart 项目。

JRuby 和 JFreeChart:快乐地在一起!

在不了解 JFreeChart 的情况下,我能够在昨晚加载它并从 IRB 获得一些简单的示例工作。 我印象深刻,所以我决定写这篇博文!

我已经将这个例子作为 headius/jruby-charts 推送到 GitHub 上,以便你可以跟着学习。

使用 jar-dependencies 获取 JFreeChart

我们将首先创建一个 jar-dependencies “Jarfile”,它大致相当于 Bundler 的 “Gemfile”:

jar 'org.jfree:jfreechart:1.5.5'

类似于 Gemfile 的 gem 名称和版本,我们指定我们想要的库的 Maven 坐标(用冒号分隔)。 一旦我们有了这个文件,我们就可以使用 lock_jars 命令来获取和“锁定”这个依赖项。

jruby-charts $ lock_jars
-- jar root dependencies --
   org.jfree:jfreechart:1.5.5:compile
Jars.lock created

此命令将 JFreeChart 和任何其他依赖项获取到你的本地 Maven 仓库,默认情况下位于 ~/.m2/repository 中。 通常,我们建议以这种方式使用 jar-dependencies,这样你的 jar 不会与其他 gems 的 jar 冲突,并且给定系统上的所有应用程序都使用同一组下载的文件。 也可以获取 jar 并将它们放在你的应用程序或 gem 中,但我们将把该示例留到以后讨论。

让我们开始有趣的部分:从 Ruby 使用 JFreeChart!

生成一个简单的条形图

我们的第一个例子将创建一个简单的 bar chart

首先,我们需要加载我们刚刚下载和锁定的 jar。

# 使用 JRuby 附带的 jar-dependencies 加载 JFreeChart
require 'jar-dependencies'
require_jar 'org.jfree', 'jfreechart', '1.5.5'

此时,JFreeChart 的所有类 都可用于 Ruby。 我们首先创建一个 DefaultCategoryDataset,它将保存我们的条形图数据。

# 创建一个空的 CategoryDataSet
bar_data = org.jfree.data.category.DefaultCategoryDataset.new

JFreeChart 提供了各种数据集类型,可以从数据库、基于 JVM 的集合对象(包括 Ruby 集合)或其他形式的结构化数据中获取数据。 “默认” 版本非常适合简单的例子。 让我们用一些数据填充它。

# 将值添加到数据集
bar_data.add_value 44, "Ben and Jerry's", "Flavors by creamery"
bar_data.add_value 31, "Baskin Robbins", "Flavors by creamery"
bar_data.add_value 11, "Cold Stone", "Flavors by creamery"

这个条形图将显示来自三个著名的冰淇淋风味供应商的冰淇淋风味数量。 这里的 add_value 方法在 Java 中是 addValue,它接受一个数字、一个列键和一个行键。

给定我们的数据集,我们现在可以请求 JFreeChart 使用 ChartFactory 类为我们创建一个基本的条形图。

# 创建具有默认设置的条形图
java_import org.jfree.chart.ChartFactory
bar_chart = ChartFactory.create_bar_chart "How Many Ice Cream Flavors?",
                     "Creamery", "Flavors", bar_data

为了方便起见,我们导入 ChartFactory 类(这基本上相当于执行 ChartFactory = org.jfree.chart.ChartFactory),然后调用 create_bar_chart 以生成具有默认设置的条形图。 我们传递的参数是图表的名称,X 轴的标签,Y 轴的标签,以及我们的数据集。

现在我们有了一个图表,我们可以使用 Java 的图形 API(默认情况下随 JDK 一起提供)输出一个 PNG 文件。

# 在内存中创建一个 500x500 的缓冲图像
bar_image = bar_chart.create_buffered_image 500, 500
# 将图像作为 PNG 写入文件
bar_file = File.open("barchart.png", "w")
javax.imageio.ImageIO.write(bar_image, "PNG", bar_file.to_outputstream)

JFreeChart 的条形图类型知道如何创建一个 Java2D BufferedImage,然后我们可以将其传递给 ImageIO 来写入它。 在这种情况下,我们甚至使用 Ruby File 作为目标,使用一些 JRuby 魔法将其转换为 Java OutputStream

在锁定我们的 jar 依赖项并编写我们的脚本后,我们可以简单地从命令行使用 JRuby 运行它。

jruby-charts $ jruby examples/barchart.rb           
2025-04-30 14:58:21.744 java[84148:2288876] +[IMKClient subclass]: chose IMKClient_Modern
2025-04-30 14:58:21.744 java[84148:2288876] +[IMKInputSession subclass]: chose IMKInputSession_Modern

根据你的平台,你可能会看到一些 “有帮助的” JVM 输出,指示图形子系统已加载。

让我们看看我们刚刚做了什么!

由 JRuby 和 JFreeChart 生成的条形图

只需几行代码,我们就完成了! 真是太有趣了!

大家都喜欢馅饼!

上面的 Java 集成演练和条形图示例应该激发你对使用 JRuby 做一些很酷的事情的兴趣,但我在这里包含一个 pie chart example 来展示一些差异:

# 使用 JRuby 附带的 jar-dependencies 加载 JFreeChart
require 'jar-dependencies'
require_jar 'org.jfree', 'jfreechart', '1.5.5'
# 创建一个空的 PieDataset
pie_data = org.jfree.data.general.DefaultPieDataset.new
# 将值添加到数据集
pie_data.insert_value 0, "Fun", 0.45
pie_data.insert_value 1, "Useful", 0.25
pie_data.insert_value 2, "Cool", 0.15
pie_data.insert_value 3, "Enterprisey", 0.10
pie_data.insert_value 4, "Exciting", 0.5
# 创建具有默认设置的饼图
pie_chart = org.jfree.chart.ChartFactory.create_pie_chart "Why JRuby?", pie_data
# 对图表进行抗锯齿处理,使其看起来更干净
pie_chart.anti_alias = true
# 访问实际的 PiePlot 以调整其他设置
pie_plot = pie_chart.plot
pie_plot.set_explode_percent "Fun", 0.20
# 在内存中创建一个 500x500 的缓冲图像
pie_image = pie_chart.create_buffered_image 500, 500
# 将图像作为 GIF 写入文件
pie_file = File.open("piechart.gif", "w")
javax.imageio.ImageIO.write(pie_image, "gif", pie_file.to_outputstream)

此示例利用了 JFreeChart 提供的一些自定义项:

这是生成的饼图:

由 JRuby 和 JFreeChart 生成的饼图

它就像馅饼一样简单!

轮到你了

在这篇文章中,你学到了以下内容:

我们不必编写任何 Java 代码,也不必调用任何讨厌的 C, Python 或 JavaScript 库。 你看到的代码和我们加载的库都与你的 Ruby 代码一起在同一个 JVM 进程中运行,并且可以轻松部署到任何具有 JDK 的系统。 真的就是这么简单!

JFreeChart 只是 Java 生态系统中的众多图表库之一,还有成千上万的其他有用库,你可以立即开始与 JRuby 一起使用。 需要生成 PDF 或 Office 文档? 尝试 OpenPDFApache Poi。 需要与不寻常的数据库集成? JDBC 通过标准 API 为你提供支持。 想要为你的整个应用程序部署单个二进制文件? JRuby 的 Warbler 项目允许你将所有内容捆绑为一个 jar 文件。

Ruby 现在面临着许多挑战,我们正在使用 JRuby 和 JVM 一个一个地解决这些挑战。 我希望你自己尝试 JRuby 并创造出一些美丽的东西!

在 Reddit 上加入讨论!

JRuby 支持和赞助

这是一个行动号召!

JRuby 的开发完全通过你慷慨的赞助以及为全球 JRuby 开发人员和企业提供的商业支持合同的销售来资助。 如果你发现我的工作令人兴奋或认为它对你的公司或你的项目很重要,请考虑与我合作以保持 JRuby 的强大和前进! Sponsor Charles Oliver Nutter on GitHub! Sign up for expert JRuby support from Headius Enterprises! Written on April 30, 2025