对于Open Flash Chart(以下简称OFC)不再作过多介绍,很多网络博客都对其作了相应介绍;这里只是记录一下自己最近几天折腾OFC一些不常用特性的经过,以免以后再次忘记,权作学习笔记。
由于下面很多地方都用到了页面嵌入的OFC对象,所以在此先定义一个获取该对象的方法:
function findSWF(movieName) { var movies = $("#" + movieName); if(movies && movies.length && movies[0].tagName !== 'DIV') return movies[0]; }
这里的参数movieName其实就是加载OFC图表SWF文件时指定的ID;如果嵌入SWF文件时没有指定即embedSWF的最后一个参数里没有类似{id:'customID'}的代码则默认会使用HTML元素的ID。
如下所示:
swfobject.embedSWF(flashPath, "chart", "100%", "100%", "9.0.0", "expressInstall.swf",{}, {},{id:'custom'}); // ID为custom swfobject.embedSWF(flashPath, "chart", "100%", "100%", "9.0.0", "expressInstall.swf",{}, {}); // ID为chart
OFC在页面上被嵌入,如果没有在嵌入SWF文件时指定data-file,当页面被访问使其加载后,会调用该JavaScript方法来寻找图表数据,所以我们可以在页面上定义该方法,并在方法实现中创造(或者其它路径,如向服务器请求数据等)JSON数据,然后将JSON数据转成String字符串返回即可。OFC取得数据便可以渲染出相应的图表内容。
示例:
function open_flash_chart_data() { $.ajax({ url: contextPath + '/ofc2/chart!barChart.action', type: 'post', data: {}, dataType: 'json', success: function(data) { findSWF("chart").load(JSON.stringify(data)); } }); return ""; }
上面这段代码是在嵌入SWF后用AJAX方式去获取数据,然后动态更新OFC图表数据。因为数据是从服务器获取的而且AJAX是异步的,所以这个open_flash_chart_data方法无法直接返回JSON字符串,而只能返回空字符串;在AJAX请求返回后才将数据传递给OFC。这其实并不实用,因为我们完全可以在嵌入SWF文件时就指定URL,由OFC自己去请求数据,这样还可以节省不少代码,写在这里只是作为一个例子以助理解。
该回调函数是在OFC载入数据之后调用的,在这个函数里可以作一些操作,比如启动一个计时器动态获取最新数据,然后使页面中的OFC动态加载最新数据,从而可以完成OFC的动态更新。
示例:
/** * 在OFC加载完成数据之后,启动定时器,2秒钟更新一次图表 */ function ofc_ready() { setInterval(function () { update(); }, 2000); }
由于OFC图表数据是在浏览器以JavaScript对象的形式进行持有(或者重新去服务器端加载数据),所以非常容易对其进行操作,然后让图表Flash动态进行加载数据,从而可以对图表动态更新。这里利用了OFC图表对象暴露的JavaScript回调接口load:
/** * 操作数据代码,依据个人需求修改数据或者重新获取等 * 这里的data参数是JSON对象,JSON.stringify方法是将其转换为String串,方法定义在json2.js中 */ findSWF("chart").load(JSON.stringify(data));
这样在数据正确的情况下,图表就会重新渲染,完成动态更新。当然图表的样式可以随意改变,不仅仅是图表内容更新,比如,我们可以将饼图变为线型图,然后再变成柱形图,也可以将柱形图的数据改变一些之后再重新渲染成柱形图,只是柱子的高低有所变化;所以渲染成什么样子完全取决于数据。
官网上说当我们需要将图表保存成图片时有两种选择:
其一、生成图片,然后上传到服务器保存起来;
其二、生成图片,然后传给JavaScript函数;
OFC暴露了一个接口用于将图片数据上传至服务器,这就是post_image()方法;接口定义如下:
/** * @param url 是一个字符串,即图片上传路径,可以跟参数.如http://example.com/ofc/ofc_upload_img.php?name=chart.jpg * @param callback 字符串类型,指定一个JavaScript方法名作为图片上传成功后的回调函数,只有当debug参数为false时可用 * @param debug Boolean类型,如果为true会重新打开一个窗口显示结果 */ post_image(url:String, callback:String, debug:Boolean);
比如我们可以这样调用:
findSWF("chart").post_image(contextPath + "/ofc2/chart!postPicture.action?name=postImage",'',false);
当我们执行这句代码时,OFC就会将图片数据提交至我们给定的URL资源,这里用的是Struts2的Action,但在Action如何接收数据我还不明确,官网上也没有找到相关例子。
交给JavaScript处理的话可操作性就比较丰富了,我们可以将图片直接显示在当前页面或者显示在新窗口;也可以将图片上传至服务器保存;甚至模拟图片下载的方式由服务器接收图片数据后转换成图片再将文件流写回页面等。不管采用何种操作,我们首先要做的就是获取图片数据,这里又用到了OFC暴露的另外一个接口:get_img_binary(),这个方法用来获取图表对应图片数据的Base64编码。
比如我们可以将图片数据直接传给一个img元素,将其渲染到HTML页面(IE7和IE7好像不支持base64编码图片显示),如:
var base64Data = findSWF("chart").get_img_binary(); // 获取图表对应图片数据的Base64编码 $("#showImg").empty().append("<img src='data:image/png;base64," + base64Data + "' />"); // 渲染一个图片元素,添加到指定的DIV中
再比如我们将获取到的Base64编码提交给服务器,服务器进行解码处理成图片后再以文件流方式写回页面,这样就类似于图片下载了:
/** * 这里将图片Base64编码放进页面的一个隐藏域,然后将该隐藏域所在的Form提交, * 这样在服务器写出文件流时会弹出文件保存对话框,也就类似于"图片另存为..." */ $("input[name='imgBase64Code']").val(findSWF("chart").get_img_binary()); $("#savePicForm").submit();
如果我们将图片的Base64编码提交提交到了服务器,那么为了得到原始数据我们需要在后台进行Base64解码,然后将解码后的数据输出到浏览器实现类似图片下载的功能或者保存下来;每种服务器语言解码方式可能都不一样,在Java语言中可以使用Base64Decoder类来解码。
示例(采用Struts2的Action,参照网络例子):
public String savePicture() throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); String imgBase64Code = request.getParameter("imgBase64Code"); response.setContentType("image/PNG;charset=UTF-8"); response.setHeader("Content-disposition", "attachment; filename=" + new String("flashExport.png".getBytes(), "iso-8859-1")); BASE64Decoder decoder = new BASE64Decoder(); byte[] buffer = decoder.decodeBuffer(imgBase64Code); for (int i = 0; i < buffer.length; ++i) { if (buffer[i] < 0) { buffer[i] += 256; // 调整异常数据 } } response.getOutputStream().write(buffer); return null; }
OFC在Flash的右键菜单中预留了一个接口:Save_Image_Locally,这个菜单项被点击时会调用页面中定义的名字为save_image的JavaScript方法。所以我们也可以将该接口作为上一步描述的JavaScript处理图表生成的图片数据的入口,比如我们定义如下的JavaScript方法:
// 点击右键菜单项中的“Save_Image_Locally”选项时触发 function save_image() { $("input[name='imgBase64Code']").val(findSWF("chart").get_img_binary()); $("#savePicForm").submit(); }
下面将一个完整示例贴在此处,示例中包含了上面描述的所有内容,例子并没有将如何生成各种各样的图表作为重点,而主要测试的是OFC跟JS之间的交互。
<script type="text/javascript" src="<%=path %>/js/jquery-1.7.2.min.js"></script> <script type="text/javascript" src="<%=path %>/js/jquery-ui.min.js"></script> <script type="text/javascript" src="<%=path %>/js/json2.js"></script> <script type="text/javascript" src="<%=path %>/js/ofc2/swfobject.js"></script>
<input type="hidden" id="contextPath" value="<%=request.getContextPath() %>" /> <div> <button onclick="chart()">加载图表</button> <button onclick="outputAsPicture()">输出为图片</button> <button onclick="save_image()">保存为图片</button> <button onclick="saveAsPicture()">保存为图片(post_image)</button> <button onclick="updateChart()">更新图表</button> <div id="content" style="width: 540px;height: 360px;border: 2px solid red;"> <div id="chart"></div> </div> <div id="showImg" style="width: 540px;height: 360px;border: 2px solid red;">图片显示于此</div> </div> <form action="<%=path %>/ofc2/chart!savePicture.action" id="savePicForm" method="post"> <input type="hidden" name="imgBase64Code" /> </form>
var contextPath; $(function (){ contextPath = $("#contextPath").val(); $("#content").resizable(); }) /** * 加载图表 */ function chart() { var flashPath = contextPath + "/js/ofc2/open-flash-chart.swf"; var dataPath = contextPath + "/ofc2/chart!barChart.action"; swfobject.embedSWF( flashPath, "chart", "100%", "100%", "9.0.0", "expressInstall.swf", {"data-file":dataPath}, {wmode:"transparent"} ); } /** * OFC获取JSON数据方法 * @returns {String} JSON字符串 */ function open_flash_chart_data() { $.ajax({ url: contextPath + '/ofc2/chart!barChart.action', type: 'post', data: {}, dataType: 'json', success: function(data) { findSWF("chart").load(JSON.stringify(data)); } }); return ""; } /** * 在OFC加载完成数据之后,启动定时器,2秒钟更新一次图表 */ function ofc_ready() { setInterval(function () { updateChart(); }, 2000); } /** * 将图表输出为图片,显示在当前页面的DIV元素中 */ function outputAsPicture() { var base64Data = findSWF("chart").get_img_binary(); $("#showImg").empty().append("<img src='data:image/png;base64," + base64Data + "' />"); } /** * 使用OFC暴露的post_image接口向服务器提交图片数据 */ function saveAsPicture() { findSWF("chart").post_image(contextPath + "/ofc2/chart!postPicture.action?name=postImage",'postDone',false); } /** * 使用OFC暴露的post_image接口向服务器提交图片数据成功后的响应函数 * @param id */ function postDone(id) { alert('post image done!the id is ' + id); } /** * 动态更新图表 */ function updateChart() { var ofc = findSWF("chart"); if(ofc) { $.ajax({ url: contextPath + '/ofc2/chart!barChart.action', type: 'post', dataType: 'json', success: function(data) { ofc.load(JSON.stringify(data)); } }); } } /** * 利用隐藏域向服务器提交图片的Base64编码数据,模拟图片另存为功能 */ function save_image() { var base64Data = findSWF("chart").get_img_binary(); $("input[name='imgBase64Code']").val(base64Data); $("#savePicForm").submit(); } /** * 获取OFC对象 * @param movieName 加载OFC时指定的id * @returns 指定的OFC */ function findSWF(movieName) { var movies = $("#" + movieName); if(movies && movies.length && movies[0].tagName !== 'DIV') return movies[0]; }
下面是服务器代码,采用Struts2的Action实现,依赖jofc2开源项目。实现了组装OFC生成柱形图所需JSON数据及图片下载等功能。类导入部分省略。
package com.zyh.action.ofc2; // import code; public class ChartAction extends ActionSupport { private static final long serialVersionUID = 1L; /** * 生成柱形图,组装OFC渲染柱形图所需数据 */ public String barChart() { BarChart barChart = new BarChart(Style.GLASS); barChart.setText("Test Here"); Random random = new Random(); XAxis xAxis = new XAxis(); xAxis.setColour("#909090"); xAxis.setGridColour("#ADB5C7"); int max = 0; for(int i = 0; i < 10; i++) { int temp = random.nextInt(100); if(max < temp) max = temp; Bar bar = new Bar(temp); barChart.addBars(bar); Label label = new Label("test" + temp); xAxis.addLabels(label); } YAxis yAxis = new YAxis(); yAxis.setMax(max + 10); yAxis.setSteps(max / 10); Chart chart = new Chart("图表测试", "{font-size: 20px; font-weight: bold; color: #A2ACBA; text-align: center;}"); chart.addElements(barChart); Text xLegend = new Text("X轴图例", "{font-size: 16px; font-weight: bold; color: #A2ACBA; text-align: center;}"); chart.setXLegend(xLegend); Text yLegend = new Text("Number", "{font-size: 16px; font-weight: bold; color: #A2ACBA; text-align: center;}"); chart.setYLegend(yLegend); chart.setXAxis(xAxis); chart.setYAxis(yAxis); try { System.out.println(chart.toString()); writeChart(chart, ServletActionContext.getResponse()); } catch (Exception e) { e.printStackTrace(); } return null; } private void writeChart(Chart chart,HttpServletResponse response) throws Exception { response.setContentType("application/json-rpc;charset=utf-8"); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Expires", "0"); response.setHeader("Pragma", "No-cache"); PrintWriter writer = response.getWriter(); writer.write(chart.toString()); writer.flush(); writer.close(); } /** * 该方法是响应OFC暴露的post_imgae()接口,但不知道如何取得图片数据,暂时闲置 */ public String postPicture() { HttpServletRequest request = ServletActionContext.getRequest(); System.out.println(request.getParameter("name")); return null; } /** * 接收图片Base64编码,解压后输出至页面,形成下载图片效果 */ public String savePicture() throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); String imgBase64Code = request.getParameter("imgBase64Code"); response.setContentType("image/PNG;charset=UTF-8"); response.setHeader("Content-disposition", "attachment; filename=" + new String("flashExport.png".getBytes(), "iso-8859-1")); BASE64Decoder decoder = new BASE64Decoder(); byte[] buffer = decoder.decodeBuffer(imgBase64Code); for (int i = 0; i < buffer.length; ++i) { if (buffer[i] < 0) { buffer[i] += 256; // 调整异常数据 } } response.getOutputStream().write(buffer); return null; } }