使用JRuby和JFreeChart创建漂亮的图表
Creating beautiful charts with JRuby and JFreeChart

原始链接: https://blog.headius.com/2025/04/beautiful-charts-with-jruby-and-jfreechart.html

本文提倡在 JRuby 中使用基于 JVM 的库,认为其优于 C 扩展或 FFI 绑定,是访问外部功能的更佳选择,并以图表绘制为例进行说明。JRuby 允许无缝使用 Java 库(例如 JFreeChart),避免了整合 C、Python 或 JavaScript 库的复杂性。 这个过程包括:通过 `jar-dependencies` 获取库及其依赖项,引入 JAR 文件,然后像调用 Ruby 类一样调用 Java 类。作者提供了使用 JFreeChart 生成条形图和饼图的示例,强调了其易用性和自定义图表设置的能力。该集成通过加载库、创建数据集、使用 `ChartFactory` 生成图表以及将其输出为图像文件来演示,所有这些都无需编写一行 Java 代码。 结论强调,数千个 Java 库可随时与 JRuby 集成,从而促进一种简洁高效的开发方法。

Hacker News 的讨论帖围绕一篇文章展开,该文章介绍了如何使用 JRuby 和 JFreeChart 创建图表。最初的评论集中在 JFreeChart 库过时的美学设计上,尤其批评其“丑陋”的 3D 效果,让人联想起 2000 年代初期的软件。用户们回忆起 JFreeChart 一成不变的设计,并将其与现代的 JavaScript 绘图库进行了对比。 随后,讨论扩展到相关话题,包括使用基于 JVM 的脚本语言(如 JRuby 和 Jython)构建 Java 应用程序的优势,其中一位用户分享了将 Jython 嵌入 Swing 应用程序的积极经验。关于饼图,特别是 3D 饼图的适用性也展开了辩论,一些人称其为“邪恶”的图表,并提倡更好的数据可视化实践。一些用户建议使用 GraalJS 和 Apache ECharts 等替代方案来实现现代化的图表解决方案。讨论还涉及到使用一个调用 Python 库的 Ruby gem 的奇怪之处,而文章作者则主张使用 JRuby 来访问 Java 库。
相关文章

原文

I recently returned from RubyKaigi where I had the opportunity to sit down with members of the Japanese Ruby community and show them a little bit of JRuby. One of the items that came up a few times was the difficulty of utilizing external libraries from Ruby: if it’s a C library, typically you have to either write a C extension or do the extra work of writing up an FFI binding.

If the library is not implemented in C or Ruby, things get even weirder.

One example is the Charty library, one of the more popular options for generating beautiful chart graphics on Ruby. But Charty actually wraps a Python library called matplotlib, and the bindings for CRuby literally load Python into the current process and call it via Ruby C API calls which then make Python C API calls. The horror!

Also recently, a Reddit Ruby post demonstrated the use of the QuickChart library, which is implemented in JavaScript… yuck! In this case, the call-out is via process launching to the QuickChart command line, but either way it’s not at really integrated into the Ruby app using it.

Is there a better way? You bet there is: use a JVM-based library from JRuby! This post will show you how to get started with JRuby’s Java integration and a few quick examples of using the JFreeChart library from Ruby code. I promise you won’t have to write a single line of Java, C, Python, or JavaScript.

The JFreeChart library provides an extensive set of chart formats, with a huge array of rendering options and easy integration with either JVM-based GUI toolkits or simple image-file output. Unlike many of the other charting libraries, it also supports live updating of a given chart GUI, making it an excellent choice for desktop monitoring tools.

Building a GUI with JRuby is fun and easy, but for this post we’ll focus on just the chart and image generation parts of the API and how you can use them easily from Ruby.

The magic starts with JRuby’s Java integration layer. The simplest way to use a Java library is to basically pretend it’s a Ruby library, with a few tweaks along the way:

  • Download the library’s jar file and dependencies, or use jar-dependencies (part of JRuby’s standard library) to fetch them for you.
  • Require the jar file and dependencies manually (a simple require "myfile.jar" works in JRuby!) or by using require_jar from jar-dependencies.
  • Call the Java classes you’re interested from Ruby as if they were plain old Ruby classes. You can even import them, Java-style, but it’s often not necessary.

Let’s start with a simple example.

Calling into the java.lang.Runtime class

Assuming you’ve installed JRuby 10, you can start playing with JVM libraries directly from IRB. Let’s fire it up and make sure we’re running on JRuby.

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

The JDK comes with a large number of built-in features, of course, and I like to use the java.lang.Runtime class for quick demonstrations. It provides methods to query a bit of information about the running JVM and the host operating system. Let’s “import” it into our session and call a few methods.

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

There’s a few bits of magic here that make it really feel like Ruby:

  • JRuby defines a few top-level methods for common Java package roots like java, javax, org, and com. We use this to reference the Java package java.lang here and access the Runtime class.
  • Runtime defines a static method (similar to a Ruby class or singleton method) called getRuntime which gets the singleton instance of Runtime for the current JVM process. JRuby turns Java “getter” and “setter” methods into Ruby “attribute accessors”, so you can just call Runtime.runtime here.
  • The Ruby calls to available_processors and free_memory translate to the Java methods getAvailableProcessors and getFreeMemory in the same way. The original methods are still callable, but we add aliases for the Ruby-friendly names.

We can accomplish a tremendous amount with the libraries shipped in the JDK, ranging from simple database access to professional desktop GUI development.

If the libraries we want to use are not shipped with the JDK, however, we need to fetch them. It’s always possible to download the library’s “jar” file directly, but then you may also have to hunt down its dependency jars. A better way is to use JRuby’s jar-dependencies library.

jar-dependencies

Like Ruby gems get pushed to rubygems.org, Java libraries get published as jar files to Maven repositories, a global, federated repository of every version of every Java library known to man. In order to make it as easy as possible to use these libraries from JRuby, we’ve built extensive Maven tooling for both Ruby and Java applications.

The simplest way to load a JVM library from Maven into your JRuby app is to use jar-dependencies, a built-in tool for fetching and managing Java dependencies in either standalone applications or in gems.

For our examples, we want to fetch JFreeChart and its dependency libraries. If we search for it at search.maven.org, we can acquire its “Maven coordinates”.

Searching Maven Central for JFreeChart

The most recently-published artifact is jfreechart 1.5.5. From here we can see the coordinates we’re looking for:

  • The group ID, used for namespacing libraries, is org.jfree.
  • The artifact ID, which identifies the library, is jfreechart.
  • And the version we want is 1.5.5 (Maven strongly encourages always using specific versions).

Given that, we can set up our JFreeChart project.

Without knowing anything about JFreeChart, I was able to load it up and get some simple examples working from IRB just last night. I was so impressed, I decided to write this blog post!

I’ve pushed this example as headius/jruby-charts on GitHub so you can follow along.

Using jar-dependencies to fetch JFreeChart

We’ll start by creating a jar-dependencies “Jarfile”, which is roughly equivalent to Bundler’s “Gemfile”:

jar 'org.jfree:jfreechart:1.5.5'

Similar to a Gemfile’s gem name and version, we specify the Maven coordinates of the library we want (separated by colons). Once we have this file, we can fetch and “lock” this dependency with the lock_jars command.

jruby-charts $ lock_jars

-- jar root dependencies --

      org.jfree:jfreechart:1.5.5:compile

Jars.lock created

This command fetches JFreeChart and any other dependencies to your local Maven repository, which by default is in ~/.m2/repository. In general, we recommend using jar-dependencies this way, so that your jars don’t conflict with other gems’ jars, and all applications on a given system use the same set of downloaded files. It’s also possible to fetch the jars and ship them inside your application or gem, but we’ll leave that example for another day.

Let’s get to the fun stuff: using JFreeChart from Ruby!

Generating a simple bar chart

Our first example will create a simple bar chart.

First, we need to load in the jars we just downloaded and locked.

# Use jar-dependencies, included with JRuby, to load JFreeChart
require 'jar-dependencies'
require_jar 'org.jfree', 'jfreechart', '1.5.5'

At this point all of the classes of JFreeChart are available to Ruby. We start by creating a DefaultCategoryDataset which will hold our bar chart data.

# Create an empty CategoryDataSet
bar_data = org.jfree.data.category.DefaultCategoryDataset.new

JFreeChart provides a wide array of dataset types that can source data from a database, a JVM-based collection object (which includes Ruby collections), or other forms of structured data. The “default” version works nicely for a simple example. Let’s fill it with some data.

# Add values to the dataset
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"

This bar chart will display a count of ice cream flavors from three well-known purveyors of the creamery arts. The add_value method here is addValue in Java, and takes a number, a column key, and a row key.

Given our dataset, we can now request that JFreeChart create a basic bar chart for us using the ChartFactory class.

# Create a bar chart with default settings
java_import org.jfree.chart.ChartFactory
bar_chart = ChartFactory.create_bar_chart "How Many Ice Cream Flavors?",
                                          "Creamery", "Flavors", bar_data

We import the ChartFactory class for convenience (which is basically equivalent to doing ChartFactory = org.jfree.chart.ChartFactory) and then call create_bar_chart to generate a bar chart with default settings. The arguments we pass are the name of the chart, the label for the X axis, and the label for the Y axis, and our dataset.

Now that we have a chart, we can use Java’s graphics APIs (provided with the JDK by default) to output a PNG file.

# Create a buffered image in memory at 500x500
bar_image = bar_chart.create_buffered_image 500, 500

# Write the image as a PNG to a file
bar_file = File.open("barchart.png", "w")
javax.imageio.ImageIO.write(bar_image, "PNG", bar_file.to_outputstream)

JFreeChart’s bar chart type knows how to create a Java2D BufferedImage, which we can then pass to ImageIO to write it out. In this case, we even use a Ruby File as the target, using a bit of JRuby magic that turns it into a Java OutputStream!

With our jar dependencies locked and our script written, we can simply run it with JRuby from a command line.

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

Depending on your platform, you may see a little bit of “helpful” JVM output indicating that the graphics subsystem has been loaded.

Let’s see what we just made!

A bar chart generated by JRuby and JFreeChart

Just a few lines of code and we’re done! Such fun!

Everyone loves pie!

The Java integration walkthrough and bar chart example above should whet your appetite for doing cool things with JRuby, but I include a pie chart example here to show a few differences:

# Use jar-dependencies, included with JRuby, to load JFreeChart
require 'jar-dependencies'
require_jar 'org.jfree', 'jfreechart', '1.5.5'

# Create an empty PieDataset
pie_data = org.jfree.data.general.DefaultPieDataset.new

# Add values to the dataset
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

# Create a pie chart with default settings
pie_chart = org.jfree.chart.ChartFactory.create_pie_chart "Why JRuby?", pie_data

# Anti-alias the chart to look a bit cleaner
pie_chart.anti_alias = true

# Access the actual PiePlot to tweak additional settings
pie_plot = pie_chart.plot
pie_plot.set_explode_percent "Fun", 0.20

# Create a buffered image in memory at 500x500
pie_image = pie_chart.create_buffered_image 500, 500

# Write the image as a GIF to a file

pie_file = File.open("piechart.gif", "w")
javax.imageio.ImageIO.write(pie_image, "gif", pie_file.to_outputstream)

This example takes advantage of a few customizations provided by JFreeChart:

  • The edges of the pie are set to anti-alias for a cleaner look (pie_chart.anti_alias = true, which calls setAntiAlias).
  • We access the actual PiePlot object to “explode” one of the elements out of the pie.
  • Instead of a PNG, we output a GIF, just because. The standard Java ImageIO support can handle BMP, GIF, JPEG, PNG, TIFF, and WBMP, and there’s third-party support for everything else.

And here’s the resulting pie chart:

A pie chart generated by JRuby and JFreeChart

It’s as easy as pie!

In this post, you learned the following:

  • Basics of Java integration in JRuby
  • How to fetch and load Java libraries from Maven
  • A few simple ways to create charts with JFreeChart

We didn’t have to write a single line of Java code, and we didn’t have to call out to any nasty C, Python, or JavaScript libraries. The code you see and the libraries we loaded all run in the same JVM process alongside your Ruby code, and can be easily deployed to any system with a JDK. It’s really that simple!

JFreeChart is just one charting library out of many in the Java ecosystem, and there’s thousands of other useful libraries you can start using with JRuby today. Need to generate PDFs or Office documents? Try OpenPDF or Apache Poi. Need to integrate with unusual databases? JDBC has you covered with a standard API. Want to deploy a single binary for your entire application? JRuby’s Warbler project allows you to bundle everything up as a single jar file.

Ruby faces many challenges these days, and we’re solving them one at a time with JRuby and the JVM. I hope you will experiment with JRuby yourself and create something beautiful!

This is a call to action!

JRuby development is funded entirely through your generous sponsorships and the sale of commercial support contracts for JRuby developers and enterprises around the world. If you find my work exciting or believe it is important your company or your projects, please consider partnering with me to keep JRuby strong and moving forward!

Sponsor Charles Oliver Nutter on GitHub!

Sign up for expert JRuby support from Headius Enterprises!

联系我们 contact @ memedata.com