构造一个条形图首先必须象在构造饼形图中那样为数据建模。条形图,和饼形图一样,有一个数据 series 数组(或多个)和一个 category 数组。我不使用 DefaultCategoryGraph2DModel 作为条形图的模型,而是将说明如何为您的数据创建一个定制模型,以及 JBarGraph 怎样在运行时期间使用这个模型生成条形图。为使这个练习更加有趣,我将向您展示如何动态地更改 series 元素的值和重画条形图。
如图 4 所示,一个定制 category 图模型必须继承 AbstractGraphModel 并实现 CategoryGraph2DModel (参见 SalesGraphModel.java )。注意:这部分涉及的方法继承自 CategoryGraph2DModel 接口。
图 4. 创建一个定制 category 图模型

表 1 展示了 CategoryGraph2DModel 接口的方法以及 JBarGraph 如何在运行时利用模型。
表 1. CategoryGraph2DModel 接口方法 描述 public abstract void addGraphDataListener(GraphDataListener)添加一个侦听器 public abstract void firstSeries()选择第 1 个数据 series public abstract String getCategory(int i)返回 第 i 个category public abstract float getValue(int i)返回 第 i 个category 的值 public abstract boolean nextSeries()选择下一个数据 series public abstract void removeGraphDataListener(GraphDataListener)除去一个侦听器 public abstract int seriesLength()返回当前 series 的长度
让我们看一下这些方法的较为详细一点的信息。
在运行时, JBarGraph 调用 public void firstSeries() ,它选择第 1 个数据 series。如果您正在处理多个数据 series,将它们存储在 Vector 中是有意义的。调用的下一个方法是 public int seriesLength() ;这个方法返回当前 series 的长度。 seriesLength() 方法返回的整型值被用于形成一个循环构造来获取每个 category 以及那个 series 的相应整型值。在这个 series 循环中,调用 public String getCategory(int i) 获取 第 i 个 category 的名称,并调用 public float getValue(int i) 获取 第 i 个category 的值。(术语 第 i 个是指 category 的顺序,比如第 1、第 2、第 3 等等。)为当前 series 收集了全部数据元素后,调用 public boolean nextSeries() 方法选择下一个数据 series。如果 nextSeries() 方法返回 true,那么为新数据 series 收集数据元素的过程将重新开始 ― 从 seriesLength() 方法开始。当 nextSeries() 返回 false 时,模型中不再有数据 series。
在 BarGraph.java 中 SalesGraphModel 继承 AbstractGraphModel 并实现 CategoryGraph2DModel 。为简单起见,将 SalesGraphModel 配置为只处理一个数据 series。如果您查看 SalesGraphModel 中的 nextSeries() 方法,您会注意到它只返回 false;这表明这个模型内只包含一个数据 series。传给 SalesGraphModel 构造器的输入参数包括 category 的名称和一个数据 series。创建 SalesGraphModel 和 JBarGraph 实例的代码可在清单 4 的 getBarGraph() 方法中找到。
清单 4. 创建一个 JBarGraph
2 public class BarGraph extends JFrame
3 implements ActionListener {
4
5 // ...
6 private static final String CATEGORY_NAMES[]
7 = { "London", "Paris", "New York" };
8 // ...
9 private static final float SALES_SERIES[]
10 = { 24.1f, 14.4f, 36.8f };
11 // ...
12 private JBarGraph getBarGraph() {
13 SalesGraphModel model
14 = new SalesGraphModel(CATEGORY_NAMES,
15 SALES_SERIES);
16 barGraph = new JBarGraph(model);
17 return barGraph;
18 }
19 // ...
20
图 5 显示 BarGraph.java 和 SalesGraphModel.java 产生的输出。您或许已注意到,所有的 category 条颜色都一样,这与饼形图示例相反,在饼形图示例中,每个 category 的颜色都不同。这是因为 JBarGraph 只允许您改变数据 series 的颜色表示,而不是 series 内的 category。
图 5. BarGraph.java 生成的条形图的输出示例
SalesGraphModel 具有允许在数据 series 内增加 category 值的功能。当用户从 BarGraph 示例选择了一个 category(通过单选按钮)并按下“Add 1 to total”按钮时, public void incrementCategoryTotal(int i) 方法被调用。这个方法把选中要增加的 category 作为方法参数传递,传递后会有一个通知被发送到 JBarGraph 实例(通过 public void dataChanged(GraphDataEvent) 方法):模型中的数据已经发生改变,必须重画此条形图。清单 5 显示了增加 category 总数的过程。
清单 5. 增加 category 总数
2 public class BarGraph extends JFrame
3 implements ActionListener {
4 private void addToCategoryTotal(int category) {
5 SalesGraphModel model
6 = (SalesGraphModel)barGraph.getModel();
7 model.incrementCategoryTotal(category);
8 }
9 // ...
10 }
11 // ...
12 public class SalesGraphModel
13 extends AbstractGraphModel
14 implements CategoryGraph2DModel {
15 // ...
16 private float seriesTotals[];
17 // ...
18 public void incrementCategoryTotal(int i) {
19 seriesTotals[i]++;
20 }
21 // ...
22 }
23
JLineGraph 的模型与 JPieChart 或 JBarGraph 的模型相似;这两个模型都有多个数据 series 和一个标识 series 内数据的 category。但 JLineGraph 的模型引入了 x 轴和 y 轴坐标系的复杂性,在这一部分,我将研究如何创建一个定制的模型,并展示 JLineGraph 是如何在运行时期间使用这个模型生成一个折线图的。注意:这一部分涉及的方法继承自 Graph2DModel 接口。
一个定制的折线图模型必须要从 AbstractGraphModel 继承并实现 Graph2DModel 接口,如表 2 中所示。
表 2. Graph2DModel 接口方法 描述 public abstract void addGraphDataListener(GraphDataListener)添加一个侦听器 public abstract void firstSeries()选择第 1 个数据 series public abstract float getXCoord(int i)返回 第 i 个category public abstract float getYCoord(int i)返回 第 i 个category 的值 public abstract boolean nextSeries()选择下一个数据 series public abstract void removeGraphDataListener(GraphDataListener)除去一个侦听器 public abstract int seriesLength()返回当前 series 的长度
这些方法的执行与表 1 中所示的相似。
在运行时期间, JLineGraph 调用 public void firstSeries() 方法;这选择第 1 个数据 series。调用的下一个方法是 public int seriesLength() ;它返回当前 series 的长度。 seriesLength() 方法返回的整型值被用于形成一个循环构造以获取那个 series 的 x 轴和 y 轴坐标值。在这个 series 循环中,调用 public float getXCoord(int i) 获取 第 i 个 category,并调用 public float getYCoord(int i) 获取 第 i 个 category 的值。为当前 series 收集了全部数据元素后,调用 public boolean nextSeries() 方法选择下一个数据 series。如果 nextSeries() 方法返回 true,那么为新数据 series 收集数据元素的过程将重新开始 ― 从 seriesLength() 方法开始。如果 nextSeries() 返回 false,那是因为模型中不再有数据 series。
缺省情况下, JLineGraph 只接受 x 轴和 y 轴的浮点值。在我的折线图中,x 轴应该包含代表一年中前六月(一月到六月)的 String 。要完成这个任务,必须发生两件事:第一,必须继承 Graph2DModel 接口以包含 public String getXLabel(float i) 方法。这样您就能够获得 x 轴的 String 表示。第二,必须继承 JLineGraph ,并覆盖 drawLabeledAxes(Graphics g) 方法。这一步将允许您使用新的接口访问 getXLabel(float f) 方法。图 6 是一个类图表, 显示创建一个带标签的折线图涉及的类。
图 6. 带标签折线图的类图表

创建 DemandGraphModel 和 JLineGraph 类的实例代码可在 getLineGraph() 方法中找到,清单 6 中显示了这些代码。
清单 6. 创建一个 JLineGraph 实例
2 public class LineGraph extends JFrame {
3 private JLineGraph lineGraph;
4 private static final String MONTH_NAMES[] = { "Jan", "Feb", //... };
5 private static final int MONTH_NUMBERING[] = {0, 1, 2, 3, 4, 5 };
6 private static final int LONDON_SERIES = 0;
7 private static final int PARIS_SERIES = 1;
8 private static final int NEW_YORK_SERIES= 2;
9 private static final float LONDON_DEMAND[] = { 1.2f, 3.7f, 6.7f, // ... };
10 private static final float PARIS_DEMAND[] = { 12.6f, 15.0f, 13.7f, // ... };
11 private static final float NEW_YORK_DEMAND[] = { 15.0f, 13.9f, 10.1f, // ... };
12 // ...
13 private JLineGraph getLineGraph() {
14 DemandGraphModel model = new DemandGraphModel();
15 model.addSeries(LONDON_DEMAND);
16 model.addSeries(PARIS_DEMAND);
17 model.addSeries(NEW_YORK_DEMAND);
18 model.setXAxisLabel(MONTH_NAMES);
19 model.setXAxis(MONTH_NUMBERING);
20 lineGraph = new LabeledLineGraph(model);
21 lineGraph.setColor(LONDON_SERIES, Color.red);
22 lineGraph.setColor(PARIS_SERIES, Color.yellow);
23 lineGraph.setColor(NEW_YORK_SERIES, Color.blue);
24 lineGraph.setXIncrement(1);
25 return lineGraph;
26 }
27 // ...
28 }
29
清单 6 的最后一行显示了对 LabeledLineGraph setXIncrement(int i) 方法的一次调用。完成这次调用是为了确保 x 轴是按照严格的增量画的。换句话说, JLabeledLineGraph 将用最大化应用程序窗口或浮动窗口的相同坐标来标记 x 轴上的每一“格”。图 7 显示了 LineGraph.java 产生的输出示例。调整应用程序窗口的大小,看看折线图是如何被重画以适合窗口新尺寸的。
图 7. LineGraph.java 生成的折线图的输出示例
结论
JSci 是一个开放源代码成果,主要用于科学应用。在本文中,我已经介绍了作为创建 2D 图形工具的 JSci 的一些可能用途。JSci 是专业绘图包的备用的免费软件。虽然 JSci 并不提供专业画图包所带的内建类型支持,您却的确可以访问源代码,并且可以自由修改代码,然后将它们提交回 JSci 社区。JSci 是一个不断发展的成果,我认为它是一个很好的备用方案,您在用 Java 创建 2D 图形时可以考虑使用它。