`
远去的渡口
  • 浏览: 468025 次
  • 性别: Icon_minigender_2
  • 来自: 上海转北京
社区版块
存档分类
最新评论

JfreeChart热点map的应用

阅读更多

    今天主要总结一下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时,感觉这篇文章写的很详细,对我也很有帮助,不过,我实现的具体方案还是经过多次尝试、多次调整,毕竟我这里的要求更高,呵呵

分享到:
评论
21 楼 fdzhuzhu 2013-08-02  
楼主,请问你最后的那个自定义提示信息部分是怎么实现的啊?能发我一份代码么
非常感谢啊,被这提示的东西弄得晕头转向啊
20 楼 easonfans 2010-12-17  
写的并不好!
原创很少,误导成分很多!取自IBM developworks上的部分资料
建议大家看看这个
http://lancui.iteye.com/blog/678081
我是看了这个之后才明白的。
19 楼 远去的渡口 2010-11-26  
leo_soul 写道
spiderwebplot.setToolTipGenerator(new StandardCategoryToolTipGenerator());  我没加这句话。现在imgMap加上了 接下来得改成自定义内容的title,并且要加入换行。
你的例子里之所以没加入这句话,或者说我其他所有非蜘蛛图的demo里没加这句话也能显示热点,原因在于那些plot类型是使用JFreeChart的工厂类直接创建该plot类型,而工厂类里是没有创建蜘蛛图的方法的,只能new 蜘蛛图。
与工厂类有参数设定是否创建热点不同,手工new蜘蛛图 只能在后面设定toolTip生成器。

恭喜你找到了解决方案哈,最近我太忙了,都忘记这事了,没有去查Jfreechart的东西,你有时间写写总结哈,分享一下,呵呵
前两天将JE的JfreeChart部分制作成了PDF电子书,http://julianna-only.iteye.com/blog/pdf,研究Jfreechart的可以收藏一下
18 楼 leo_soul 2010-11-26  
spiderwebplot.setToolTipGenerator(new StandardCategoryToolTipGenerator());  我没加这句话。现在imgMap加上了 接下来得改成自定义内容的title,并且要加入换行。
你的例子里之所以没加入这句话,或者说我其他所有非蜘蛛图的demo里没加这句话也能显示热点,原因在于那些plot类型是使用JFreeChart的工厂类直接创建该plot类型,而工厂类里是没有创建蜘蛛图的方法的,只能new 蜘蛛图。
与工厂类有参数设定是否创建热点不同,手工new蜘蛛图 只能在后面设定toolTip生成器。
17 楼 leo_soul 2010-11-15  
远去的渡口 写道
leo_soul 写道
已经放弃特效。
热点在任务列表中已经排到最后。
现在的jfreechart,demo已经不允许免费查看源代码。api查过,无收获。

不知道JfreeChart1.8有没有蜘蛛图,我有时间瞧瞧。

内牛满面,加上自定义热点的话回复一下吧。谢谢啦。
16 楼 远去的渡口 2010-11-13  
leo_soul 写道
已经放弃特效。
热点在任务列表中已经排到最后。
现在的jfreechart,demo已经不允许免费查看源代码。api查过,无收获。

不知道JfreeChart1.8有没有蜘蛛图,我有时间瞧瞧。
15 楼 leo_soul 2010-11-10  
已经放弃特效。
热点在任务列表中已经排到最后。
现在的jfreechart,demo已经不允许免费查看源代码。api查过,无收获。
14 楼 远去的渡口 2010-11-05  
leo_soul 写道
1、我尝试过了在蜘蛛图 SpiderWebPlot中加入热点。可是无论用ChartUtilities.writeImageMap还是用ChartUtilities.getImageMap得到的 map标签 均是标签体里什么都没有 没有<area>项目,在<map></map>之间只有一个"/r/n",所以没有热点效果。同样的代码只要更改plot类型就能产生<area>。

2、我这里的需求是,要求生成的图片还带有一定特效。举例比如蜘蛛图,某几组数据差距很小,所以点与点很容易重叠,另外由于数据项生成的先后顺序,会导致后生成数据的项目在蜘蛛图中间的充色区域覆盖住之前的数据项。尽管这些颜色是半透明的,但由于数据差距小,比较模糊。所以要求特效可以是:指向某个点的时候,该点的及其同组数据产生的折线闭合区图层顶置(姑且理解为图层吧),或者,指向某点,该点所处的闭合曲线高亮,或者,有动作效果。特效方式不限,总之要求所指向的闭合曲线明显与其他。
请问这俩该怎么实现?
实在是很难办。请求帮助。

问题现在解决了么?实在不好意思,蜘蛛图我没有用过,所以帮不上了。。。具体的怎么实现可以多看看API。
13 楼 远去的渡口 2010-11-05  
每种画图方法都有特定的属性,你可以参考一下demo,重要的还是多看看API,具体的怎么实现我没做过你所说的蜘蛛图,所以没有发言权,希望我写这个的时候你已经解决了之前的问题,可以分享一下,呵呵
12 楼 leo_soul 2010-11-01  
其实我们使用的dojo.chart 。但是由于dojo.chart没有蜘蛛图,所以只能使用jfreechart单生成一个蜘蛛图了。
11 楼 leo_soul 2010-11-01  
1、我尝试过了在蜘蛛图 SpiderWebPlot中加入热点。可是无论用ChartUtilities.writeImageMap还是用ChartUtilities.getImageMap得到的 map标签 均是标签体里什么都没有 没有<area>项目,在<map></map>之间只有一个"/r/n",所以没有热点效果。同样的代码只要更改plot类型就能产生<area>。

2、我这里的需求是,要求生成的图片还带有一定特效。举例比如蜘蛛图,某几组数据差距很小,所以点与点很容易重叠,另外由于数据项生成的先后顺序,会导致后生成数据的项目在蜘蛛图中间的充色区域覆盖住之前的数据项。尽管这些颜色是半透明的,但由于数据差距小,比较模糊。所以要求特效可以是:指向某个点的时候,该点的及其同组数据产生的折线闭合区图层顶置(姑且理解为图层吧),或者,指向某点,该点所处的闭合曲线高亮,或者,有动作效果。特效方式不限,总之要求所指向的闭合曲线明显与其他。
请问这俩该怎么实现?
实在是很难办。请求帮助。
10 楼 远去的渡口 2010-10-02  
joeygh 写道
我也用过一阵JFreeChart,不过我感觉太麻烦,做报表无非就是那几个东西,但我现在用的是FusionChart,这个东西做出的图漂亮,自己也可以封装。

是的,FusionChart也比较漂亮一些。这个我也了解过一些的,不过公司当时没有选择用FusionChart,企业版的是收费的,free版的好象有些功能不是很全。
9 楼 joeygh 2010-10-01  
我也用过一阵JFreeChart,不过我感觉太麻烦,做报表无非就是那几个东西,但我现在用的是FusionChart,这个东西做出的图漂亮,自己也可以封装。
8 楼 leo_soul 2010-09-30  
约等于重新画图了,明白了。lz。

我看你的例子也糊涂了。我需要做出来的web应用是,在一个小的div里用jfreechart画一个图片,并且不能在服务器生成图片,也就是不能用saveChartAsXXXX这样的方法,只能用writeChartAsXXX在response的输出流里写图片,那该如何加入map呢?map作用只是在鼠标指向数据点的时候提示个数值,但是要求提示框是和你最后例子的外观一致(提示框背景白色)。
你的map是在服务器生成一个文件了吧?能否不生成文件 实现我那个需求啊?
7 楼 远去的渡口 2010-09-29  
leo_soul 写道
强人啊。我正为这个发愁呢。太强了lz。拜读啊!
能否顺便写一个关于 如果做动态图的方法?我发现jfreechart构成各种图的时候,真的就是生成图片,那这些图片肯定是静态的,比如我想多加一条数据集 或者 修改一下坐标内容。能否动态的更新呢?还是简单的手工重新创建图???
请求lz写写此方面的研究文章。写完了请给我一个回复 我去拜读!

JfreeChart的确存在这个问题,JfreeChart的原理就是根据你提供的数据源,生成你指定的图片,所以不管是你所说的“静态”的还是“动态”的都是生成新的图片,也就是说,如果你用JfreeChart,除非你改源码,那么都只能是用这种机制完成。
其实,你提的这种需求当时我们的网站就有所体现,我当时是用ajax实现的,添加一条数据后,再调用一次画图方法,得到最新的图片, 这样给用户的感觉就是数据增加时,图片至少多了一个点或者内容不同了。
6 楼 leo_soul 2010-09-29  
强人啊。我正为这个发愁呢。太强了lz。拜读啊!
能否顺便写一个关于 如果做动态图的方法?我发现jfreechart构成各种图的时候,真的就是生成图片,那这些图片肯定是静态的,比如我想多加一条数据集 或者 修改一下坐标内容。能否动态的更新呢?还是简单的手工重新创建图???
请求lz写写此方面的研究文章。写完了请给我一个回复 我去拜读!
5 楼 远去的渡口 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等。
4 楼 se7en8974 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字符串。
3 楼 se7en8974 2010-09-28  
我也迷茫,楼主有文档没?
2 楼 远去的渡口 2010-08-23  
happydrive 写道
楼主,我看了之后还是有点迷茫,能上点完整的代码。

JfreeChart map用到的代码就是这我贴出来的这些,两个java区代码拼在一起就是map这个功能。热点map是在你的JfreeChart图画出来后添加的。我这里没有贴出如何得到数据源,如何画图的代码。

相关推荐

Global site tag (gtag.js) - Google Analytics