论坛首页 Java企业应用论坛

JfreeChart热点map的应用

浏览 12210 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (1) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-01-15   最后修改:2010-01-15

    今天主要总结一下JfreeChart中热点map的应用,根据我个人的理解来总结的,可能会有某些地方不是很正确,如有错误,请大家指正,不断改进。

     首先,这里的热点的应用在于用户与JfreeChart图像的交互,也就是与图像文件(因为我们是将Chart对象以二进制数据写入一个图像文件了,比如PNG文件,JPEG图像文件)交互。在HTML中,为了让一个图像具有可交互的功能就必须给该图像定义一个Map对象,然后在

<img id="chartimage" src="${linechart }" usemap="#${filename }" >

也就是在img标签的usemap属性上,指定map对象。

下面举例说明,用JfreeChart在这个图像中产生热点。

 

那么它的原始map数据大致为:

<map id="jfreechart-23846.png" name="jfreechart-23846.png">

<area shape="poly" coords="675,93,681,93,681,99,675,99,675,93,675,93" title="日平均攝取熱量: (09-12-28 上午12:00, 1,788)" alt=""/>

<area shape="poly" coords="276,96,282,96,282,102,276,102,276,96,276,96" title="日平均攝取熱量: (09-11-27 上午12:00, 1,741.8)" alt=""/>

<area shape="poly"

<area shape="poly" coords="225,97,231,97,231,103,225,103,225,97,225,97" title="日平均攝取熱量: (09-11-23 上午12:00, 1,722.4)" alt=""/>

<area shape="poly" coords="115,24,114,26,112,27,110,26,109,24,110,22,112,21,114,22,115,24,115,24" title="日平均消耗熱量: (09-11-14 上午12:00, 3,027.8)" alt=""/>

</map>

这里面,shape是指热点的区域,本段中的poly是一个多边形,也有Rectangle等。Coords是这个多边形的坐标,通常是需要用工具生成,这里所展示的是JfreeChart自动生成的。Title=””中的内容便是mouse移到各个点时,展示出的信息,mouse移开后信息不再显示。这里不需要我们再写别的代码,个人理解是,浏览器可以解析这部分数据,相当于将title的内容在onmouseover中显示。

   那么,现在的问题就在于,怎么获得map数据了。

    

当然是根据一个图像来生成对应的MAP对象。

在创建一个图表对象时候有两个参数,比如这里面的折线图的部分代码:

JFreeChart jfreechart = ChartFactory.createTimeSeriesChart(title, "",

          y, xydataset, true, true, false);

两个参数就是这个方法中的最后两个参数,这两个参数的类型都是布尔值。这两个参数意思分别是:是否创建工具提示(tooltip)以及是否生成URL。这两个参数分别对应着MAP中一个AREAtitle属性以及href属性。

可是我想知道的是怎么来产生这个MAP啊!哈哈,不要着急,JFreeChart已经帮我们做好生成MAP对象的功能。为了生成MAP对象就要引入另外一个对象:ChartRenderingInfo。因为JFreeChart没有直接的方法利用一个图表对象直接生成MAP数据,它需要一个中间对象来过渡,这个对象就是ChartRenderingInfo。下图是生成MAP数据的流程图:

 

如上图所示,ChartUtilities类是整个流程的核心,它周围的对象都是一些例如数据对象或者是文件等。这个流程简单描述如下:首先创建一个ChartRenderingInfo对象并在调用ChartUtilitieswriteChartAsJPEG时作为最后一个参数传递进去。调用该方法结束后将产生一个图像文件以及一个填充好MAP数据的ChartRenderingInfo对象,有了这个对象我们还是没有办法获取具体的MAP数据,我们还必须借助于ChartUtilitieswriteImageMap方法来将ChartRenderingInfo对象读取出来,获取MAP数据的代码片断如下:

 

JFreeChart chart = createCaloriesLineChart(timesers, weekormonth,
				title, avgenergy + "(" + y + ")", index, week, year1,year,
				nodatamess, maxcalorie, month);

		HttpServletRequest request = io.getRequest();
		String filename = "";
		String graphURL = "";

		PrintWriter writer = null;
		try {
			io.getResponse().setContentType("text/html");
			io.getResponse().setCharacterEncoding("UTF-8"); // 这里要设置一下,因为这里相当于用PrintWriter将图片写出来,如果不设置字符集,则默认为ISO-8859-1,而页面用的是UTF-8.
			writer = io.getResponse().getWriter();
			Shape shape = new Rectangle(10, 5);
			ChartEntity entity = new ChartEntity(shape);
			StandardEntityCollection coll = new StandardEntityCollection();
			coll.add(entity);
			ChartRenderingInfo info = new ChartRenderingInfo(coll);
			filename = ServletUtilities.saveChartAsPNG(chart, 720, 250, info,
					io.getSession());
			ChartUtilities.writeImageMap(writer, filename, info, true);

			graphURL = request.getContextPath() + "/displayChart?filename="
					+ filename;

			
			String strimg = ChartUtilities.getImageMap(filename, info);

 

 

当初在实现这个拿到map数据时,花费了不少时间,根据writeImageMap()回溯到找寻如何得到 info,要得到info又如何得到StandardEntityCollection对象,以及StandardEntityCollectionChartEntityShape的关联,这个就需要花时间好好研究API了,感觉JfreeChart API写的太过于简单,需要自己动手试试一些方法才可以完成,不过感觉这部分还是比较单一的需求,感觉在图象对象其他的类上,过于复杂,文档过于简单,我们必须去了解每个类型的图表对象应该对应哪些AxisPlotRenderer类,并且必须非常熟悉这些类的构造函数中每个参数的具体含义才可以少走弯路,对于某些特定的需求,比如柱状图与折线图并存时,可不可以用XYPlot,只有试过,根据报错的信息才知道这样做是不可以的。好了,别的感慨不多说了。这里的strimg的内容就是map对象。

 

自己实现响应mouse事件

上图的map热点信息,大家可以看出,过于简单、表达的方式的不常规很难满足产品的需求,至少要以比较直观的、清晰的方式来表达信息,并且,如果单单只写出XY坐标对应的值,相信map热点的存在就是多余,just a Demo.所以为了满足产品级别需求,我们需要自己实现响应mouse事件。

   这个解决方案有多种,我是根据自己的理解,将很规则的map数据解析,根据X轴时间取到关联这一天、周、月的详细信息,然后将解析、重组后的数据以变量传到onmouseover事件中。

当然,怎么样让数据与map中各<area>标签的内容一致,以及后续的Tip定位问题,也是需要考虑的。为了方便,这里需要给每个area节点再加一个属性id,并赋值。

StringReader reader = new StringReader(strimg);
			InputSource source = new InputSource(reader);
			SAXBuilder build = new SAXBuilder();

			try {
				Document doc = build.build(source);
				Element element = doc.getRootElement();

				List<Element> list = element.getChildren();

				int size1 = list.size();
				for (int j = 0; j < size1; j++) {

					String nstr = "";

					Element e = list.get(j);
					Attribute att = e.getAttribute("title");
					String value = att.getValue();
					value = value.replace("(", "");
					value = value.replace(")", "");

					Attribute bute = new Attribute("id", j + "");
					e.setAttribute(bute);

					int id = bute.getIntValue();

					int ind = j;
					if (j >= size1 / 2) {
						ind = j - (size1 / 2);
					}

					UserFoodRecordBean r = (UserFoodRecordBean) calories
							.get(ind);

					double bike = r.getBikeCalorie();
					double bmrto = r.getBmrTotal();
					double sport = r.getSpcal();
					double foodcal = r.getCaloriesimple();
					double fect=r.getFoodfec();
					double cyclomer=r.getCycal();
					double total1 = r.getSportTotalCalorie();
					double balance = foodcal - total1;
					double dailywork = r.getDailyworkcalorie();
					String datestr = r.getUserfoodDate();

					if (weekormonth == 0) {
						datestr = BarChartData.formAtDate(datestr);
					} else {
						String[] das = datestr.split("-");
						int weekindex = Integer.parseInt(das[1]);

						if (weekormonth == 1) {
							
							datestr = das[0] + year + index1 + weekindex + week;
						} else {
							datestr = das[0] + year1  + weekindex + month;

						}
					}

					balance = Double.parseDouble(BarChartData
							.formAtCalorie(balance));

					nstr += "date=" + datestr + "&BMR=" + bmrto + "&";
					if (bike > 0) {
						nstr += "e-Bike=" + bike + "&";
					}
					if (sport > 0) {
						nstr += "sport=" + sport + "&";
					}
					if(cyclomer>0){
						nstr+="cyclemer="+cyclomer+"&";
					}
					if(fect>0){
						nstr+="fect="+fect+"&";
					}

					nstr += "food=" + foodcal + "&" + "total=" + total1
							+ "&balance=" + balance + "&dailywork=" + dailywork;

					// nstr=HtmlUtils.htmlUnescape(nstr);

					att.setValue("");// set nstr to title

					String over = "showChartTips('" + nstr + "','" + id
							+ "',event);";
					String out = "chartlivess();";

					Attribute bu = new Attribute("onmouseover", over);

					e.setAttribute(bu);

					Attribute outatt = new Attribute("onmouseout", out);
					e.setAttribute(outatt);

				}

				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				PrintWriter pw = new PrintWriter(baos); // 用PrintWriter可以解决生成编码为UTF-8格式的XML内容出现中文乱码的问题


				Format format = Format.getPrettyFormat();
				format.setEncoding("UTF-8");

				XMLOutputter output = new XMLOutputter();
				output.setFormat(format);

				output.output(doc, pw);

				String strs = baos.toString();
				int j = strs.indexOf("<map");
				strs = strs.substring(j);
				io.setData("intakemap", strimg, BeanShare.BEAN_SHARE_REQUEST);

				

			} catch (JDOMException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

 

 

 

 

然后以什么样式展示就与自己的审美有关了……当然可以是美工提供模版,呵

具体的过程就不用累赘了。

看看这样实现后的效果:

 

另外,在实现的过程中,还有一个问题也是花了许多时间 tip的定位问题,要兼容IE6IE7IE8FireFox,并且,出现滚动条时tip可能会错位问题,部分代码如下:

 

 var area= document.getElementById(id);
   if(area){
      var scroleft=document.documentElement.scrollLeft;
      var left=e.clientX+scroleft;
      var topy=e.clientY;
      var top= document.documentElement.scrollTop + topy;
  
  left-=66;
   
    var _tips=createTip1(); 
      _tips.style.width ="27em";
     // _tips.style.height="190px";
      _tips.style.display= ""; 
      
      _tips.style.left = left+"px";    
      _tips.style.top = top+"px"; 
  _tips.innerHTML=document.getElementById("tipmess").innerHTML;
  
   if(navigator.userAgent.indexOf('Firefox') >= 0){
    top=window.scrollY + e.clientY;
    left=window.scrollX+e.clientX;
    _tips.style.left=left+"px";
      _tips.style.top = top+"px"; 
      }
   
  
  }

 

 

PS: 部分内容转自http://www.ibm.com/developerworks/cn/java/l-jfreechart/ 作者 刘冬

 如果大家之前没有接触过JfreeChart或者是初学者,可以先看看这篇文章 。当初我在实现这个热点map时,感觉这篇文章写的很详细,对我也很有帮助,不过,我实现的具体方案还是经过多次尝试、多次调整,毕竟我这里的要求更高,呵呵

   发表时间:2010-08-21  
楼主,我看了之后还是有点迷茫,能上点完整的代码。
0 请登录后投票
   发表时间:2010-08-23  
happydrive 写道
楼主,我看了之后还是有点迷茫,能上点完整的代码。

JfreeChart map用到的代码就是这我贴出来的这些,两个java区代码拼在一起就是map这个功能。热点map是在你的JfreeChart图画出来后添加的。我这里没有贴出如何得到数据源,如何画图的代码。
0 请登录后投票
   发表时间:2010-09-28  
我也迷茫,楼主有文档没?
0 请登录后投票
   发表时间:2010-09-28   最后修改:2010-09-28
map:
<map id="jfreechart-52374.png" name="jfreechart-52374.png">
<area shape="poly" coords="691,445,691,60,703,52,752,52,752,437,740,445,740,445" title="(时间范围, 9884) = 2,617" alt="" nohref="nohref"/>
<area shape="poly" coords="626,445,626,155,638,147,687,147,687,437,675,445,675,445" title="(时间范围, 9810) = 1,968" alt="" nohref="nohref"/>
<area shape="poly" coords="561,445,561,111,573,103,622,103,622,437,610,445,610,445" title="(时间范围, 9813) = 2,265" alt="" nohref="nohref"/>
<area shape="poly" coords="496,445,496,141,508,133,557,133,557,437,545,445,545,445" title="(时间范围, 9850) = 2,064" alt="" nohref="nohref"/>
<area shape="poly" coords="431,445,431,109,443,101,492,101,492,437,480,445,480,445" title="(时间范围, 9846) = 2,283" alt="" nohref="nohref"/>
<area shape="poly" coords="366,445,366,145,378,137,427,137,427,437,415,445,415,445" title="(时间范围, 9847) = 2,036" alt="" nohref="nohref"/>
<area shape="poly" coords="301,445,301,70,313,62,362,62,362,437,350,445,350,445" title="(时间范围, 9803) = 2,543" alt="" nohref="nohref"/>
<area shape="poly" coords="236,445,236,138,248,130,297,130,297,437,285,445,285,445" title="(时间范围, 9795) = 2,085" alt="" nohref="nohref"/>
<area shape="poly" coords="170,445,170,75,182,67,232,67,232,437,220,445,220,445" title="(时间范围, 9827) = 2,511" alt="" nohref="nohref"/>
<area shape="poly" coords="105,445,105,148,117,140,167,140,167,437,155,445,155,445" title="(时间范围, 9802) = 2,015" alt="" nohref="nohref"/>
</map>

不是说有map文件么 我打印的map字符串。
0 请登录后投票
   发表时间:2010-09-29  
se7en8974 写道
map:
<map id="jfreechart-52374.png" name="jfreechart-52374.png">
<area shape="poly" coords="691,445,691,60,703,52,752,52,752,437,740,445,740,445" title="(时间范围, 9884) = 2,617" alt="" nohref="nohref"/>
<area shape="poly" coords="626,445,626,155,638,147,687,147,687,437,675,445,675,445" title="(时间范围, 9810) = 1,968" alt="" nohref="nohref"/>
<area shape="poly" coords="561,445,561,111,573,103,622,103,622,437,610,445,610,445" title="(时间范围, 9813) = 2,265" alt="" nohref="nohref"/>
<area shape="poly" coords="496,445,496,141,508,133,557,133,557,437,545,445,545,445" title="(时间范围, 9850) = 2,064" alt="" nohref="nohref"/>
<area shape="poly" coords="431,445,431,109,443,101,492,101,492,437,480,445,480,445" title="(时间范围, 9846) = 2,283" alt="" nohref="nohref"/>
<area shape="poly" coords="366,445,366,145,378,137,427,137,427,437,415,445,415,445" title="(时间范围, 9847) = 2,036" alt="" nohref="nohref"/>
<area shape="poly" coords="301,445,301,70,313,62,362,62,362,437,350,445,350,445" title="(时间范围, 9803) = 2,543" alt="" nohref="nohref"/>
<area shape="poly" coords="236,445,236,138,248,130,297,130,297,437,285,445,285,445" title="(时间范围, 9795) = 2,085" alt="" nohref="nohref"/>
<area shape="poly" coords="170,445,170,75,182,67,232,67,232,437,220,445,220,445" title="(时间范围, 9827) = 2,511" alt="" nohref="nohref"/>
<area shape="poly" coords="105,445,105,148,117,140,167,140,167,437,155,445,155,445" title="(时间范围, 9802) = 2,015" alt="" nohref="nohref"/>
</map>

不是说有map文件么 我打印的map字符串。

没错,是字符串,并不是文件。得到map字符串后,你再处理这个字符串,整合得到你想要的格式,比如hashmap或者Arraylist等。
0 请登录后投票
   发表时间:2010-09-29   最后修改:2010-09-29
强人啊。我正为这个发愁呢。太强了lz。拜读啊!
能否顺便写一个关于 如果做动态图的方法?我发现jfreechart构成各种图的时候,真的就是生成图片,那这些图片肯定是静态的,比如我想多加一条数据集 或者 修改一下坐标内容。能否动态的更新呢?还是简单的手工重新创建图???
请求lz写写此方面的研究文章。写完了请给我一个回复 我去拜读!
0 请登录后投票
   发表时间:2010-09-29  
leo_soul 写道
强人啊。我正为这个发愁呢。太强了lz。拜读啊!
能否顺便写一个关于 如果做动态图的方法?我发现jfreechart构成各种图的时候,真的就是生成图片,那这些图片肯定是静态的,比如我想多加一条数据集 或者 修改一下坐标内容。能否动态的更新呢?还是简单的手工重新创建图???
请求lz写写此方面的研究文章。写完了请给我一个回复 我去拜读!

JfreeChart的确存在这个问题,JfreeChart的原理就是根据你提供的数据源,生成你指定的图片,所以不管是你所说的“静态”的还是“动态”的都是生成新的图片,也就是说,如果你用JfreeChart,除非你改源码,那么都只能是用这种机制完成。
其实,你提的这种需求当时我们的网站就有所体现,我当时是用ajax实现的,添加一条数据后,再调用一次画图方法,得到最新的图片, 这样给用户的感觉就是数据增加时,图片至少多了一个点或者内容不同了。
0 请登录后投票
   发表时间:2010-09-30  
约等于重新画图了,明白了。lz。

我看你的例子也糊涂了。我需要做出来的web应用是,在一个小的div里用jfreechart画一个图片,并且不能在服务器生成图片,也就是不能用saveChartAsXXXX这样的方法,只能用writeChartAsXXX在response的输出流里写图片,那该如何加入map呢?map作用只是在鼠标指向数据点的时候提示个数值,但是要求提示框是和你最后例子的外观一致(提示框背景白色)。
你的map是在服务器生成一个文件了吧?能否不生成文件 实现我那个需求啊?
0 请登录后投票
   发表时间:2010-10-01  
我也用过一阵JFreeChart,不过我感觉太麻烦,做报表无非就是那几个东西,但我现在用的是FusionChart,这个东西做出的图漂亮,自己也可以封装。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics