技术开发 频道

JSci:Java2D绘图的开放源代码供选方案

  构造一个条形图首先必须象在构造饼形图中那样为数据建模。条形图,和饼形图一样,有一个数据 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

1 // ...
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 总数 

1 // ...
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 实例

1 // ...
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 图形时可以考虑使用它。

  代码下载

0
相关文章