构建可扩展的Java图表组件.doc_第1页
构建可扩展的Java图表组件.doc_第2页
构建可扩展的Java图表组件.doc_第3页
构建可扩展的Java图表组件.doc_第4页
构建可扩展的Java图表组件.doc_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

构建可扩展的Java图表组件前言Java语言所具有的面向对象特性,使许多复杂的问题可以分解成相对独立的对象来处理。本文用面向对象的方法,将一个图表组件从分解到如何组合,以及如何进行扩展作了详细的讲解。从简单的折线图到稍复杂的多种形状组合的图表,读者可以学到构建一个可扩展的图表组件是多么的容易。常见的图表类型图表具有很直观的视觉效果,可以方便的用来比较数据的差异、图案和趋势等。从外观上来看,常用到的图表主要有散点图、(折)曲线图、柱状图等。本文主要讨论这几种图形样式。其中这每种图又可以与其它的类型组合产生更多的形式。下面以图例来说明:先来看散点图:图1-1图1-1是一个典型的散点图,它是由一组X值和一组Y值在二维坐标中两两成对描绘而成。一般这种图形反映两组数据的相关性。例如,要考查钢的硬度与淬火温度的关系,假设上图的横轴表示淬火的温度,纵轴表示同时测出的钢的硬度,这时我们可从上图看出一个趋势,即淬火的温度越高,钢的硬度越大。 再来看一个折线图:图1-2图1-3在图1-2的折线图中,假设横轴表示周一到周日,纵轴表示某商场的日销售额。我们可以看出其临近周末的销售额呈急剧上升趋势,到周日开始回落,而最惨淡的是周四。通常折线图也可以表示成柱状图的形式,如图1-3。复杂一点的图形图1-4图1-5图1-6 上图三个图形的数据都是同样的,但它们所能够直观表达的意思又不尽相同。诸如此类的图表,形式多种多样,但它们都是由这几种基本图表组合而成的。接下来的一节,我们来看一下组成图表的基本元素有哪些。图表的主要元素图表的组成从前面的例子中我们可以看出,每种图表都是由横坐标轴,纵坐标轴,还有不同的绘图形状组成。为了更容易理解,大家看一下下面的分解图: 上图2-1 下图2-2是一个柱状图和折线图的组合图表,我们将它分解之后(图2-2),可以清晰的看到,它是由图表区、坐标轴、网格线、图表形状等组成:图表区(Chart):包含所有其它的图表元素。 坐标轴(Axis):提供绘图形状的坐标参考。一个图表中通常有一个垂直和一个水平坐标轴。而网格线是以坐标轴的刻度为参考,贯穿整个绘图区。网格线同坐标轴一样也可分为水平和垂直网格线。 图表形状(Plot):也是以坐标轴为参考,按一定的比例将数据按相应形状绘制出来。所以,从根本上来说,一个图表的是由三种基本的可视元素组成的:图表区,坐标轴,图表形状。实现基本图表元素基本图表元素的特征我们已经知道了图表的主要组成元素,现在再来看看这些元素有哪些特征。还是来看一个图:图2-3从图上我们可以看出,一个位于屏幕坐标系中的图表具有宽度(Wc)和高度(Hc)以及坐标位置(x,y)。图表中的坐标轴也有高度Ha、宽度Wa及坐标位置(x,y)。同样,图表形状也有相应的高度Hp和宽度Wp和坐标位置。一个图表通常拥有一个横坐标轴和纵坐标轴。所有的绘图数据的坐标都要转化成适当的屏幕坐标,于是我们需要一个新的元素:比例尺。比例尺应负责完成实际坐标值到屏幕坐标值以及屏幕坐标值到实际坐标值的相互转化。而坐标轴是用来描绘刻度用的,它应与比例尺成对使用。一个图表还可以有多个图表形状(如图1-6和图2-1),并且我们可以往图表里面增加或移除形状。一个图表形状应可以表示至少一组以上的数据(如图1-5)。由于图表形状要在图表上描绘数据,它需要有一个东西来记录数据,我们将它称之为数据序列。 基本图表元素的设计实现 我们的目标是用程序来实现一个图表。前面的讨论我们已经知道构成图表的基本的元素和它们的特性了。由此我们可以为这几个图表元素设计几个接口类。在设计之前,要首先说明一下,我们不打算实现类似于商业化图表组件的强大交互功能,我们所有的设计,只是为了能阐明问题。图表元素接口(ChartWidget)因为所有的图表可视元素都有一些共同的属性:位置,宽度和高度,它们还要负责绘制自己本身。所以我们设计一个ChartWidget接口,其它所有可视元素都要继承于这个接口。这个接口的类图如图2-4:图2-4由这个类图,我们可以很容易的写出它的代码:public interface ChartWidget public int getX(); public int getY(); public int getWidth(); public int getHeight(); public void draw(Graphics g); 坐标轴(Axis) 接下来的一个类是坐标轴Axis。坐标轴主要任务是绘制轴及其刻度(Tick)和刻度值,因为它绘制时是按一定的比例绘制的,所以它需要有一个比例尺将实际坐标值转换值成屏幕坐标值。这就引出了Scale这个类。Scale类主要完成实际坐标值到屏幕坐标值以及屏幕坐标值到实际坐标值的相互转化。由此,Axis与Scale是一对相互依赖的类。从设计模式的角度来看,Axis是视图(View),负责界面绘制,Scale就是它的模型(Model),负责提供相应的数据。它们的类图见图2-5: 图2-5下面来分别看看Axis类与Scale类的代码: public abstract class Axis implements ChartWidget protected Scale scale; protected int x; protected int y; protected int width; protected int height; protected Axis peerAxis; protected boolean drawGrid; protected Color gridColor; protected Color axisColor; protected int tickLength; protected int tickCount; public Axis() gridColor = Color.LIGHT_GRAY; axisColor = Color.BLACK; tickLength = 5; drawGrid = false; public int getTickCount() return tickCount; public void setTickCount(int tickCount)this.tickCount=tickCount; public Scale getScale() return scale; public void setScale(Scale scale) this.scale = scale; public int getX() return x; public void setX(int x)this.x = x; public int getY() return y; public void setY(int y)this.y = y; public int getHeight() return height; public void setHeight(int height)this.height = height; public int getWidth() return width; public void setWidth(int width)this.width = width; public boolean isDrawGrid()return drawGrid; public void setDrawGrid(boolean drawGrid)this.drawGrid=drawGrid; 软件开发网 public Color getAxisColor()return axisColor; public void setAxisColor(Color axisColor) this.axisColor=axisColor; public Color getGridColor()return gridColor; public void setGridColor(Color gridColor)this.gridColor=gridColor; public int getTickLength()return tickLength; public void setTickLength(int tickLength)this.tickLength=tickLength; public Axis getPeerAxis()return peerAxis; public void setPeerAxis(Axis peerAxis)this.peerAxis = peerAxis;protected abstract int calculateTickLabelSize(Graphics g);public abstract class Scale protected double min; protected double max; protected int screenMin; protected int screenMax; public abstract int getScreenCoordinate(double value); public double getActualValue(int value) double vrange = max - min; if(min 0.0 & max 0.0) vrange = (min - max) * -1.0; double i = screenMax - screenMin; i = (double)(value - screenMin) * vrange) / i; i = min; return i; public void setMax(double max)this.max = max; public void setMin(double min)this.min = min; public double getMax()return max; public double getMin()return min; public int getScreenMax()return screenMax; public int getScreenMin()return screenMin; public void setScreenMax(int screenMax)this.screenMax =screenMax; public void setScreenMin(int screenMin)this.screenMin = screenMin;在上面的Axis类代码中,我们在原有的ChartWidget接口的基础上,为Axis添加了几个其它的属性:轴线的颜色axisColor,网格线的颜色gridColor及网格线的可见属性drawGrid。还有刻度线的长度和个数tickLength和tickCount。而peerAxis属性是参考坐标轴,在绘制坐标轴时的会用到。 Scale类也是抽象的,因为横轴和纵轴的屏幕坐标的转换方式不一样,所以getScreenCoordinate()方法留待子类来实现它。图表形状(Plot)组成图表还有一个最重要的类,负责描述数据的图表形状,我们称之为Plot。Plot应能绘制多组数据,而这组数据呢,我们专门用一个模型来描述它,这就是DataSeries。由于我们在这里讨论的是二维图表,所以DataSeries应能提供两组分别代表X和Y坐标的数据。还是来看看它们的类图(图2-6): 图2-6为了plot能绘制多组数据,除了从ChartWidget继承来的draw(Graphics)方法外,plot还提供了draw(Graphics,DataSeries,int)方法,用来绘制单组的数据。下面的代码更能说明问题: public abstract class Plot implements ChartWidgetprotected int x; protected int y; protected int width; protected int height; protected XAxis xAxis; protected YAxis yAxis; protected ArrayList dataSeries;public int getX()return x; public int getY()return y; public int getWidth()return width; public int getHeight()return height; public void addDataSeries(DataSeries ds)dataSeries.add(ds); public void removeDataSeries(DataSeries ds)dataSeries.remove(ds); public void draw(Graphics g) for( int i=0;idataSeries.size();i )draw(g,(DataSeries)dataSeries.get(i),i); public abstract void draw(Graphics g,DataSeries ds,int index); Plot类也被设计成了抽象类,具体的绘制方法由子类为实现。而DataSeries类的过于简单,在此我们就不列出代码了。图表(Chart)最后就是将上面的元素合成一个完整的图表,即Chart类。一个Chart有一个横轴和一个纵轴以及至少一个Plot,并且可以为它添加多个Plot。我们最后来看一下整个Chart及其相关类的UML关系图:图2-7由于篇幅有限,在此就不列出Chart类的代码了。 完成一个折线图由于前面介绍的只是一些接口或抽象类,要完成一个图表组件,还必须实现它们,下面我们以一个折线图为例,来完成一个完整的折线图。 软件开发网 实现x轴和y轴其实前面的Axis抽象类已经完成一个大部分的操作,余下的就是分别完成x轴和y轴的绘制了。在这里我们就不打算列出完整的类代码,只列出关键的实现部分。Public class XAxis extends Axis public void draw(Graphics g) if ( ! (scale instanceof XScale) ) return; int ticks = getTickCount(); int tickDist = (int) (double)(scale.getScreenMax()-scale.getScreenMin()/(double)(ticks 1); int tickX = scale.getScreenMin(); int tickY = peerAxis.getScale().getScreenMin(); int gridLength = peerAxis.getScale().getScreenMax(); int axisLength = scale.getScreenMax()-scale.getScreenMin(); /*设置轴线颜色*/ g.setColor(axisColor); /*绘制横轴*/ g.drawLine(tickX, tickY, tickX axisLength,tickY); for ( int i = 0 ; i ticks; i ) 软件开发网 tickX = scale.getScreenMin() tickDist*(i 1); if ( isDrawGrid() ) /*如果drawGrid属性为true,用gridColor绘制网格线*/ g.setColor(gridColor); g.drawLine(tickX, tickY , tickX, gridLength );/*绘制刻度线*/ g.setColor(axisColor); g.drawLine(tickX, tickY , tickX, tickY tickLength); int tickLabelWidth = g.getFontMetrics().stringWidth(String.valueOf(i 1); int tickLabelHeight = g.getFontMetrics().getHeight(); g.drawString(String.valueOf(i 1), tickX-(tickLabelWidth/2), tickY tickLabelHeight); public class YAxis extends Axis public void draw(Graphics g) if ( ! (scale instanceof YScale) ) return; int ticks = getTickCount(); int tickDist = (int) Math.abs(double)(scale.getScreenMax() - scale.getScreenMin()/(double)(ticks 1); int tickY = scale.getScreenMin(); int tickX = peerAxis.getScale().getScreenMin(); int gridLength = peerAxis.getScale().getScreenMax(); int axisLength = scale.getScreenMax(); /*绘制纵坐标轴*/ g.setColor(axisColor); g.drawLine(tickX, tickY, tickX, axisLength); for ( int i = 0 ; i ticks; i ) tickY = scale.getScreenMin()-tickDist*(i 1); if ( isDrawGrid() ) /*如果drawGrid属性为true,用gridColor绘制网格线*/ g.setColor(gridColor); g.drawLine(tickX, tickY , gridLength, tickY );/*绘制刻度线*/ g.setColor(axisColor); g.drawLine(tickX, tickY , tickX-tickLength, tickY); int tickLabelWidth = g.getFontMetrics().stringWidth(String.valueOf(i 1); g.drawString(String.valueOf(i 1), tickX-tickLength-tickLabelWidth, tickY); 实现画折线的LinePlot由于Plot是由DataSeries为它提供绘图数据的,在实现LinePlot之前,先来实现一个DefaultDataSeries类: public class DefaultDataSeries extends DataSeries public DefaultDataSeries(Object yData) throws InvalidDataException super(); if ( yData = null | !(yData0 instanceof Double) ) throw new InvalidDataException(); for ( int i = 0;iyData.length;i ) /*将y值添加到序列中*/this.yData.add(yDatai);/*根据y值的个数,从1开始自动添加相应数量的x值*/ this.xData.add(new Double(i 1); 这个DefaultDataSeries提供了一个构造方法,使用者只需提供一组y坐标值,即可构造一个DataSeries了。下面是很重要的部分了。我们来看看实现一个画折线的LinePlot是多么的简单:Public class LinePlot extends Plot public void draw(Graphics g, DataSeries ds, int index) if ( ds = null ) return; g.setColor(lineColor); double x = new doubleds.size(); double y = new doubleds.size(); int xPoints = new intds.size(); int yPoints = new intds.size(); for ( int i = 0; i ds.size(); i ) xi = (Double)ds.getXData(i).doubleValue();yi = (Double)ds.getYData(i).doubleValue();/*将ds中的实际值转换成屏幕坐标值*/ xPointsi = xAxis.getScale().getScreenCoordinate(xi); yPointsi = yAxis.getScale().getScreenCoordinate(yi); /*绘制折线*/ g.drawPolyline(xPoints, yPoints, xPoints.length); 上面可出了LinePlot中绘制折线的代码,我们看到,绘制一个折线是多么的轻松和简单。 完成折线图通过前面的实现代码,我们来看一个完整的折线图示例:double y = new double 12.5,14.1,13.2,11.4,13.25,12.32 ; try DataSeries ds = new DefaultDataSeries(Primary2ObjectUtil.Doulbe2Object(y); XAxis xaxis = new XAxis(new XScale(0,y.length 1),ds.size(); YAxis yaxis = new YAxis(new YScale(10,15),4); xaxis.setDrawGrid(true); yaxis.setDrawGrid(true); LinePlot plot = new LinePlot(ds,xaxis,yaxis); Chart chart = new Chart(xaxis,yaxis,plot); JFrame frame = new JFrame(Line Plot Demo); frame.setSize(400,300); frame.getContentPane().add(chart); frame.setVisible(true); catch (InvalidDataException e) e.printStackTrace(); 下面是这个程序运行起来的屏幕截图: (单组数据的折线图) (有多组数据的折线图) 扩展其它类型的图表通过前面的例子,我们知道要实现特定类型的图表,只要实现特定的Plot类就可以了。如果数据有特殊格式,只需再扩展一个DataSeries就可以了。为使大家加深理解,我们再以一个柱状图为例子作讲解。在第一节的图1-2和图1-3中,我们知道,一组数据除了用折线图表示之外,还可以表示成柱状图的形式。在这里我们就借用折线图的数据,来实现一个BarPlot。下面列出了BarPlot的关键代码:public class BarPlot extends Plot public void draw(Graphics g, DataSeries ds, int index) if ( ds = null ) return; /*每组柱子的个数*/ int bars = this.dataSeries.size(); /*出每个柱子应有的宽度*/ int barWidth = (int) (double)xAxis.width/(double)ds.size() 1)/bars-barSpace); if ( barWidth =0 ) barWidth = 1; int barx,bary,barw,barh; int barGroupWidth = barWidth*bars; double ymin = yAxis.getScale().getMin(); for ( int i = 0;ids.size(); i ) barx = (int)(xAxis.getScale().getScreenCoordinate(i 1) - barGroupWidth/2.0d) index*barWidth; double val = (Double)ds.getYData(i).doubleValue(); bary = yAxis.getScale().getScreenCoordinate(val); if ( ymin0) if ( val0 ) barh = bary-yAxis.getScale().getScreenCoordinate(0); bary = bary-barh; else barh = yAxis.getScale().getScreenCoordinate(0)-bary; else barh = yAxis.getScale().getScreenCoordinate(ymin)-bary; barw = barWidth; g.setColor(barColor); g.fillRect(barx,bary,barw, barh); g.setColor(Color.BLACK); g.drawRect(barx,bary, barw, barh); BarPlot的实现比LinePlot稍微复杂一点。主要是要计算每个柱子的位置,宽度和高度。由于考虑到多组柱子以及柱子的值为负数时坐标不同,所以计算要繁索一点。但总体来说,实现BarPlot也是相当简单的。由于柱状图运行代码与折线图类似,这里就不列出演示代码。下面来看看程序在几种情况下的运行画面:(单组数据的柱状图)(多组数据的柱状图)(有负值的柱状图)现在我们有了画折线图的类LinePlot和画柱状图的类BarPlot。我们要生成一个折线图与柱状图组合起来的例子。还是来看看代码是如何实现的: DataSeries ds = new DefaultDataSeries(Primary2ObjectUtil.Doulbe2Object(y1); XAxis xaxis = new XAxis(new XScale(0,y1.length 1),ds.size(); YAxis yaxis = new YAxis(new YScale(10,15),4); xaxis.setDrawGrid(true); yaxis.setDrawGrid(true); LinePlot linePlot = new LinePlot(ds,xaxis,yaxis);BarPlot barPlot = new BarPlot(ds,xaxis,yaxis);/*先生成Bar

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论