鸽子的小窝

java精灵

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  0 Posts :: 4 Stories :: 0 Comments :: 0 Trackbacks

Web 技术的发展使得基于互联网的应用程序不再局限于提供静态或者简单动态的内容。传统的一些以软件包形式发布应用程序例如报表系统等都在逐渐搬到互联网上。但是这两者之间有着天壤之别,虽然在数据获取、业务处理等方面基本类似,但是最大的差别在于用户界面。为了能在Web浏览器上显示,要求用户界面使用 HTML以及图片的方式来展现数据,而传统的一些利用操作系统本身的控件来开发的用户界面无法适应琳琅满目的客户端。

在 Web浏览器上查看图表一般有两种做法:第一种就是使用Applet利用Java本身对图形的支持来显示一个图表;第二种就是直接在Web服务器端生成好图表图片文件后发送给浏览器。第一种方式显然对于客户端要求太高,随着现在主流浏览器放弃对Java的支持后,这种方式只适合一些局域网的应用,而对于互联网的环境就显得不太适合。因此我们将介绍一个Java绘制图形组件用来产生基于Web的图表。

该组件是基于图表引擎JFreeChart上开发的,JFreeChart本身功能强大,它是一个开源的Java项目,它主要用来开发各种各样的图表,这些图表包括:饼图、柱状图(普通柱状图以及堆栈柱状图)、线图、区域图、分布图、混合图、甘特图以及一些仪表盘等。这些不同式样的图表可以满足目前商业系统的要求。该组件基于JFreeChart并做了适当优化,如中文字符处理等,但主要是让您可以更方便快速地生成您想要的各种图形。该组件在国家广电总局的《全国有线电视网络质量调研数据分析系统》及北京国电华北电力工程有限公司的《生产管理系统》中都得到很好的应用。图3-1是一个列表的饼图表现方式。

图3-1  列表的饼图表现方式

3.1  Java绘图技术基础

我们的绘图组件的核心也是基于Java绘图技术,本节我们简单讲解传统的Java绘图技术以及页面动态图像生成技术,以便读者对Java绘制图形功能有大概了解。

下面内容可能有点烦琐,也与我们使用的组件并无太大关联,主要是为了给有兴趣的读者参考,如果您是初学者或者想立即使用我们的绘图组件,完全可以跳过本小节,因为我们的组件只需要利用简单的十行左右的代码就可绘制一张图形。

3.1.1  传统的绘图技术

用Java绘图一直都吸引着开发人员的注意。传统上,Java开发人员使用java.awt.Graphics或Java 2D API进行绘图。下面我们简单介绍使用Draw2D编写自己的代码来制图或绘图。

Draw2D简介

Draw2D是一个驻留在SWT Composite之上的轻量级窗口小部件系统。一个Draw2D实例由一个SWT Composite、一个轻量级系统及其内容的图形组成。图形是Draw2D的构建块。关于Draw2D API的所有细节,可以从Draw2D Developer’s Guide的Eclipse帮助文件中找到。因为本文不打算成为一篇讲述Draw2D的教程,所以,为了简便起见,只要您了解Draw2D API可以帮助您在SWT Canvas上进行绘图就足够了。您可以直接使用一些标准的图形,比如Ellipse、Polyline、RectangleFigure和 Triangle,或者,您可以扩展它们来创建自己的图形。此外,一些容器图形,如Panel,可以充当所有子图形的总容器。

Draw2D有两个重要的包:org.eclipse.draw2d.geometry和org.eclipse.draw2d.graph,本文中使用了这两个包。 org.eclipse.draw2d.geometry包有一些有用的类,比如Rectangle、Point和PointList,这些类都是自我解释的。另一个包org.eclipse.draw2d.graph开发人员使用的可能不是太多。这个包提供了一些重要的类,比如 DirectedGraph、Node、Edge、NodeList和EdgeList,这些类有助于创建图表。

在本小节中,我们将解释如何使用Draw2D编写代码,帮助您以图形的方式形象化您的数据。我将从一项技术的描述开始,该技术将位于某一范围内的数据值(比如,从0到2048)按比例缩放成另一范围内的等效数据值(例如,从0到100)。然后,我们将举例说明如何绘制出任意个级数的X-Y坐标图,每个级数都包含一组数据元素。在学习了本小节中的概念之后,就可以很容易地绘制其他类型的图表,比如饼图和条形图。具体的绘图过程如下所示。

*  您想绘制什么样的图形?

显然,您想以图形方式描绘来自数据源的数据。所以,您需要那些您想以图形形式形象化的数据。为了简便起见,我们使用了一个名为dataGenerator的简单函数生成的数据,而不是从XML文件或其他一些数据源读取数据,该函数使用了一个for(;;)循环,并以数组列表的形式返回生成的值。

代码段1.生成一些数据

private ArrayList dataGenerator() {

  double series1[] = new double[5]; 

      for(int i=0; i<series1.length; i++)

       series1[i] = (i*10) + 10; // a linear 

       series containing 10,20,30,40,50

      double series2[] = new double[9]; 

      series2[0] = 20; series2[1] = 150; series2[2] = 5;

      series2[3] = 90; series2[4] = 35;  series2[5] = 20;

      series2[6] = 150; series2[7] = 5; series2[8] = 45;

      double series3[] = new double[7]; 

      for(int i=0; i<series3.length; i++)

       series3[i] = (i*20) + 15;

      seriesData.add(series1);

      seriesData.add(series2);

      seriesData.add(series3); 

                   return seriesData;

 }

  缩放技术——从给定的数据生成X坐标和Y坐标

一些新的术语

FigureCanvas

Draw2D中的FigureCanvas是SWT Canvas的一个扩展。FigureCanvas可以包含Draw2D图形。

Panel

Panel是Draw2D中的一个通用容器图形,它可以包含子图形。您可以向一个Panel图形中添加许多图形,然后将这个Panel图形提供给FigureCanvas。

DirectedGraph

DirectedGraph是一个2-D图形,拥有有限数量的Node,每个Node都位于一些Point中,相邻的Node是通过Edges彼此连接在一起的。

当您想绘制一架2-D飞机上的点时,必须找出每个点的X坐标和Y坐标。绘图的奇妙之处在于能够将某一个给定数据值从一个范围按比例缩放到另一个范围,也就是说,如果给定一组值,如{10,20,30},那么您应该能够确定2-D飞机上具体哪些点(X坐标和Y坐标)表示的是10、20和30这些数据值。绘制总是在按照某一个限定缩放比例进行的。换句话说,在同一限定区域内,可以绘制任意数量的点。因为该区域是固定的,所以您总是可以找到X坐标轴的跨度(长度)和Y坐标轴的跨度(高度)。X坐标轴和Y坐标轴的跨度只是等式的一部分。另一部分是找出数据值的范围,并根据每个数据值在新范围内的等效值来计算这些值的坐标。

计算X坐标和Y坐标

X坐标:X坐标是某一个点距离原点的水平距离。计算元素的数量,然后将X坐标轴的跨度分成n个区段,其中,n是给定集合中元素的数量,通过这种方式,可以计算某一集合中所有点的横向坐标。用这种分割方法可以获得每个区段的长度。集合中的第一个点位于等于区段长度的第一段距离内。后续的每个点则位于区段长度加上原点到前一个点的距离的那一段距离内。例如,给出一个集合 {10,20,30,40},您立刻就可以知道要绘制4个点,因为集合中包含4个元素。所以,应该将X坐标轴的跨度分成4个相等的区段,每个区段的长度=跨度/4。因此,如果X坐标轴的跨度是800,那么区段的长度将是800/4,即200。第一个元素(10)的X坐标将是200,第二个元素(20)的X坐标将是400,依此类推。

代码段2.计算X坐标

private int[] getXCoordinates(ArrayList seriesData){

 int xSpan = (int)GraFixConstants.xSpan;

           int longestSeries = Utilities.getLongestSeries(seriesData);

           int numSegments = 

           ((double[])seriesData.get(longestSeries)).length;        

           int sectionWidth = 

           (int)xSpan / numSegments; //want to divide span of xAxis

           int xPositions[] = 

           new int[numSegments]; // will contain X-coordinate of all dots.

 for(int i=0; i<numSegments; i++){

  xPositions[i]= 

  (i+1)*sectionWidth;//dots spaced at distance of sectionWidth   

 }

 return xPositions;

}

Y坐标:Y坐标是某一个点距离原点的纵向距离。计算Y坐标要将某一个值按比例从一个范围缩放到另一个范围。例如,给出相同的集合{10,20,30,40},您可以看出,数据的范围是0到40,新的范围就是Y坐标轴的跨度(高度)。假设Y坐标轴的高度为400,那么第一个元素(10)的高度将是100,第二个元素的高度将是200,依此类推。

通过以下例子,您可以更好地理解如何按比例将一个值从一个范围缩放到另一个范围:假定一个范围的跨度是从0到2 048,而您打算将该范围内的任意值(比如说1 024)缩放到从0到100的范围,那么您立刻就可以知道,等刻度值是50。该缩放所遵循的三值线算法是:

2048 / 1024 equals 2.

100-0 equals 100.

100 / 2 equals 50, which is the desired scaled value.

  您想在哪儿进行绘图?

您可以通过扩展Eclipse ViewPart和使用SWT Composite来创建您自己的视图。此外,也可以使用从main()函数中调用的SWT shell。在扩展Eclipse ViewPart时,必须至少实现两个函数:createPartControl(Composite parent)和setFocus()。函数createPartControl (Composite parent)是在屏幕上绘制视图时自动调用的。您的兴趣只在所接收的SWT Composite上。因此,将它传递给某个类,然后通过对这个类进行编码来绘制图形。

代码段3. 使用Eclipse ViewPart绘图

public class MainGraFixView extends ViewPart{

 public void createPartControl(Composite parent) {

  //create or get data in an arraylist

  ArrayList seriesData = dataGenerator();

  //instantiate a plotter, and provide data to it.

  DirectedGraphXYPlotter dgXYGraph = new DirectedGraphXYPlotter(parent);

  dgXYGraph.setData(seriesData);

  dgXYGraph.plot(); //ask it to plot   

 }

 public void setFocus() { 

 }

}

  您需要绘制哪种图形?

一旦拥有了数据并想用来绘制图形的区域时,就必须确定您需要哪种类型的可视化。在本文中,我们演示了如何编写代码来创建X-Y坐标图和线形图。一旦知道了绘制X-Y坐标图的技术,就应该能够绘制出其他图形,比如条形图和饼图。

  创建自己的X-Y坐标图。

X-Y坐标图应该能够绘制出2-D飞机上任意数量的级数线。每个级数线都应该以图形形式显示出引用线X和引用线Y的那些级数中每个点的位置。每个点都应该通过一条线连接到级数中的下一个点上。通过使用表示一个点和一条线的Draw2D图形,您应该能够创建这样一个坐标图。例如,为了表示一个点,我通过扩展Ellipse图形创建了一个Dot图形,并使用PolylineConnection图形来表示连接线。

DirectedGraphXYPlotter类只有两个公共函数:setData(ArrayList seriesData)和plot()。setData(ArrayList seriesData)函数接受您想要以图形形式形象化的数据(参见),而plot()函数则开始绘图。

一旦调用了plot()函数,就必须依次采用以下步骤:

—   采用一个SWT Composite,并将FigureCanvas放在它之上。然后,将一个类似Panel的通用容器图放在画布上;

—   计算将要绘制的级数的数量,然后填充创建DirectedGraphs所需数量的NodeLists和EdgeLists;

—   在Panel图上绘制X坐标轴和Y坐标轴,创建和级数一样多的DirectedGraphs,以便进行绘图。

代码段4.plot() 函数

public void plot(){

    //if no place to plot, or no data to plot, return.

     if(null==_parent || null==_seriesData)

         return;

    Composite composite = new Composite(_parent, SWT.BORDER);

    composite.setLayout(new FillLayout());

     FigureCanvas canvas = new FigureCanvas(composite);

    Panel contents = new Panel();//A Panel is a general purpose container figure

   contents.setLayoutManager(new XYLayout());

     initializeSpan(contents.getClientArea());

     populateNodesAndEdges();     

     drawAxis(contents);

    for(int i=0; i<_numSeries; i++){

    drawDotsAndConnections(contents,getDirectedGraph(i));

   }

   canvas.setContents(contents);

}

plot()函数调用了两个重要的内部函数来帮助绘制图形中的点:populateNodesAndEdges()和drawDotsAndConnections()。在您发现这两个函数到底完成什么功能之前,让我们先来看一下DirectedGraph。DirectedGraph是什么?为了使用Draw2D进行绘图,事实上您必须先创建一个图形,定义将要绘制的点和线。一旦创建好这个图形,就可以使用它实际在画布上进行绘图。您可以将DirectedGraph形象化为拥有有限数量的Node的一个2-D图形,在该图形中,每个Node都位于一些Point上,相邻的Node是通过Edges连接在一起的。您可以通过以下代码行来了解创建DirectedGraph的关键所在。首先,创建一个Node列表和一个Edges列表。然后,创建一个新的 DirectedGraph,并通过刚才创建的NodeList和EdgeList设置其成员(Nodes和Edges)。接下来使用 GraphVisitor访问这个DirectedGraph。

创建DirectedGraph的示例代码类似于下面这样:

代码段5.示例DirectedGraph

//This is a sample, you will need to add actual Node(s) to this NodeList.

NodeList nodes = new NodeList(); //create a list of nodes.

//This is a sample, you will need to add actual Edge(s) to this EdgeList.

EdgeList edges = new EdgeList(); //create a list of edges. 

DirectedGraph graph = new DirectedGraph();

graph.nodes = nodes;

graph.edges = edges;

new BreakCycles().visit(graph);//ask BreakCycles to visit the graph.

//now our "graph" is ready to be used.

现在,已经知道DirectedGraph包含许多Node,其中,每个Node都可能包含一些数据,并且还存储了这些数据的X坐标和Y坐标,以及一个Edges的列表,每个Edge都知道在自己的两端分别有一个Node,您可以通过以下技术使用这些信息来绘图,其中涉及两个部分:

部分A——通过以下步骤填充Node和Edge:

创建一个NodeList,在该列表中,集合中的每个元素都有一个Node,集合{10,20,30,40} 需要4个Node。找出每个元素的X坐标和Y坐标,将它们存储在node.x和node.y成员变量中。创建一个EdgeList,在该列表中,有n-1个Edge,其中,n是集合中元素的数量。例如,集合{10,20,30,40}需要3个Edge。将Node与每个Edge的左右端相关联,并相应地设置edge.start和edge.end成员变量。

部分B——通过以下步骤绘制表示Node和Edge的图形:

绘制一个Dot图来表示每个Node。

绘制一个PolylineConnection图形来表示每个Edge。

界定每个PolylineConnection图形,以固定Dot图的左右端。

现在,回到内部函数的工作上来:

函数populateNodesAndEdges()实现了该技术的部分A;而函数drawDotsAndConnections()则实现了该技术的部分B。

函数populateNodesAndEdges()计算将绘制多少级数。它为每个级数创建了一个NodeList和一个EdgeList。每个NodeList都包含一个用于特殊级数的Node列表。每个Node都保存着关于应该在什么地方绘制X坐标和Y坐标的信息。

函数getXCoordinates()和getYCoordinates()分别用于检索X坐标值和Y坐标值。使用中的相同算法,这些函数也可以内部地将数据值按比例从一个范围缩放到另一个范围。每个EdgeList都包含一个用于特殊级数的Edges的列表。每个Edge的左右端上都分别有一个Node。

代码段6.populateNodesAndEdges() 函数

private void populateNodesAndEdges(){      

  _seriesScaledValues = new ArrayList(getScaledValues(_seriesData));

  _nodeLists = new ArrayList();

  _edgeLists = new ArrayList();

  for(int i=0; i<_numSeries; i++){

   _nodeLists.add(new NodeList());// one NodeList per series.

   _edgeLists.add(new EdgeList());// one EdgeList per series.

  }

  //populate all NodeLists with the Nodes. 

  for(int i=0; i<_numSeries; i++){//for each series

   double data[] = (double[])_seriesData.get(i);//get the series

   int xCoOrds[] = getXCoordinates(_seriesData);

   int yCoOrds[] = getYCoordinates(i, data);

   //each NodeList has as many Nodes as points in a series

   for(int j=0; j<data.length; j++){

    Double doubleValue = new Double(data[j]);

    Node node = new Node(doubleValue);

    node.x = xCoOrds[j];

    node.y = yCoOrds[j];    

    ((NodeList)_nodeLists.get(i)).add(node);

   }     

  }

  //populate all EdgeLists with the Edges. 

  for(int i=0; i<_numSeries; i++){

   NodeList nodes = (NodeList)_nodeLists.get(i);   

   for(int j=0; j<nodes.size()-1; j++){

    Node leftNode = nodes.getNode(j);

    Node rightNode = nodes.getNode(j+1);

    Edge edge = new Edge(leftNode,rightNode);

    edge.start = new Point(leftNode.x, leftNode.y); 

    edge.end = new Point(rightNode.x, rightNode.y);

    ((EdgeList)_edgeLists.get(i)).add(edge);

   }  

  }

  int breakpoint = 0;

 }

一旦populateNodesAndEdges()函数完成了它的使命,为所有将要绘制的级数创建了NodeLists和EdgeLists,另一个函数drawDotsAndConnections()就开始为每个Node绘制一个Dot图形,并为每个Edge绘制一个PolylineConnection图形。

代码段7.drawDotsAndConnections()、drawNode() 和drawEdge()函数

private void drawDotsAndConnections(IFigure contents, DirectedGraph graph){

     for (int i = 0; i < graph.nodes.size(); i++) {

         Node node = graph.nodes.getNode(i);

         drawNode(contents, node);

     }

     for (int i = 0; i < graph.edges.size(); i++) {

         Edge edge = graph.edges.getEdge(i);

         drawEdge(contents, edge);

     }  

 }

 private void drawNode(IFigure contents, Node node){

   Dot dotFigure = new Dot();

   node.data = dotFigure;

   int xPos = node.x;

   int yPos = node.y;  

   contents.add(dotFigure);

   contents.setConstraint(dotFigure, new Rectangle(xPos,yPos,-1,-1));

 }

 private void drawEdge(IFigure contents, Edge edge){

     PolylineConnection wireFigure = new PolylineConnection();

  //edge.source is the Node to the left of this edge

     EllipseAnchor sourceAnchor = new EllipseAnchor((Dot)edge.source.data);

  //edge.target is the Node to the right of this edge

  EllipseAnchor targetAnchor = new EllipseAnchor((Dot)edge.target.data);

     wireFigure.setSourceAnchor(sourceAnchor);

     wireFigure.setTargetAnchor(targetAnchor);

     contents.add(wireFigure);     

 }

如果您想以图形的形式描绘将要展示的数据,那么Draw2D是一个好工具。可以使用Draw2D编写用来绘制图形的Java代码,这有助于您将精力集中于缩放代码和绘制代码上,把其他与绘制相关的工作留给 Draw2D和SWT。您还可以通过使用所选择的Draw2D图形来控制图形的外观。Draw2D简化了绘图的基本步骤,并且可以最大限度地减少您对第三方工具箱的依赖。

3.1.2  动态图像生成技术

在Web应用中,经常需要动态生成图片,比如实时股市行情,各种统计图等,在这种情况下,图片只能在服务器内存中动态生成并发送给用户,然后在浏览器中显示出来。

本质上,浏览器向服务器请求静态图片如jpeg时,服务器返回的仍然是标准的http响应,只不过http头的contenttype不是text/html,而是image/jpeg而已,因此,我们在 servlet中只要设置好contenttype,然后发送图像的数据流,浏览器就能正确解析并显示出图片。

在Java中,java.awt和java.awt.image包提供了基本的绘制图像的能力,我们可以在内存中绘制好需要的图形,然后编码成jpeg或其他图像格式,最后发送给相应的浏览器即可。下面是使用servlet动态创建图像的详细步骤:

  创建bufferedimage对象,该对象存在于内存中,负责保存绘制的图像;

  创建graphics2d对象,该对象负责绘制所需的图像;

  当绘制完成后,调用com.sun.image.codec.jpeg包的jpeg编码器对其编码;

  最后将编码后的数据输出至httpresponse即可。

com.sun.image.codec.jpeg包位于jdk目录的rt.jar包中,它不是公开的api,需要将rt.jar复制到Web应用程序的web-inf/lib下。

我们先创建一个最简单的servlet:

public class createimageservlet extends httpservlet {

protected void doget(httpservletrequest request, httpservletresponse response)

  throws servletexception, ioexception

  {

    response.setcontenttype("image/jpeg");

  }

}

我们首先设置了response的contenttype为image/jpeg,这样浏览器就可以正确识别。

然后,创建一个大小为100像素´100像素的bufferedimage对象,准备绘图:

int width = 100;

int height = 100;

bufferedimage bi = new bufferedimage(width, height, bufferedimage.type_int_rgb);

接着,bufferedimage对象获取graphics2d对象并绘图:

graphics2d g = bi.creategraphics(); // 创建graphics2d对象

// 填充背景为白色:

g.setbackground(color.blue);

g.clearrect(0, 0, width, height);

// 设置前景色:

g.setcolor(color.red);

// 开始绘图:

g.drawline(0, 0, 99, 99); // 绘制一条直线

// 绘图完成,释放资源:

g.dispose();

bi.flush();

然后,对bufferedimage进行jpeg编码:

jpegimageencoder encoder = jpegcodec.createjpegencoder(out);

jpegencodeparam param = encoder.getdefaultjpegencodeparam(bi);

param.setquality(1.0f, false);

encoder.setjpegencodeparam(param);

try {

encoder.encode(bi);

}

catch(ioexception ioe) {

ioe.printstacktrace();

}

编码后的jpeg图像直接输出到了out对象中,我们只要传入response. getoutputstream()中就可以直接输出到httpresponse。

下面是完整的代码:

package com.crackj2ee.web.util;

import java.io.*;

import java.awt.*;

import java.awt.image.*;

import javax.servlet.*;

import javax.servlet.http.*;

import com.sun.image.codec.jpeg.*;

public class createimageservlet extends httpservlet {

protected void doget(httpservletrequest request, httpservletresponse response)

  throws servletexception, ioexception

  {

    response.setcontenttype("image/jpeg");

    createimage(response.getoutputstream());

  }

  private void createimage(outputstream out) {

    int width = 100;

    int height = 100;

bufferedimage bi = new bufferedimage(width, height, bufferedimage.type_int_rgb);

  graphics2d g = bi.creategraphics();

  // set background:

  g.setbackground(color.blue);

  g.clearrect(0, 0, width, height);

  // set fore color:

  g.setcolor(color.red);

  // start draw:

  g.drawline(0, 0, 99, 199);

  // end draw:

  g.dispose();

  bi.flush();

  // encode:

  jpegimageencoder encoder = jpegcodec.createjpegencoder(out);

  jpegencodeparam param = encoder.getdefaultjpegencodeparam(bi);

  param.setquality(1.0f, false);

  encoder.setjpegencodeparam(param);

  try {

    encoder.encode(bi);

  }

  catch(ioexception ioe) {

    ioe.printstacktrace();

   }

  }

}

最后编译这个servlet,注册到web.xml中,映射路径为/createimage,编写一个简单的index.html测试:

<html><head></head>

<body>

<img src="createimage">

</body></html>

posted on 2011-01-11 13:24 twins331 阅读(637) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航: