-- 作者:SCYANGYU
-- 发布时间:2/19/2005 7:02:00 PM
-- [转帖]JAVA生成SVG图表实例之柱状图
JAVA生成SVG图表实例之柱状图 做系统的时候都要配合报表展示一些图表,例如柱状图、饼图还有曲线图,以前是用VML做成通用的,数值的计算和VML在页面上的显示,全部是用javascript做的,可是VML中的文字定位不是特别舒服,javascript计算数字也不是很精确。最近学了几天SVG,觉得不错,所以就想用JAVA生成SVG文件,然后显示,网上没有类似的文章,所以我就把自己的第一个作品放出来,希望大家能够提点向我提电意见,改进。我以后会陆续放做好的饼图、曲线图。我相信这是国内第一个类似的程序,希望能够起到抛砖引玉的作用。 这个程序能够生成单柱柱状图,也能生成多柱柱状图。我的注释写得相当的详细,相信大家都能看的懂。我没有使用APACHE的Batik来生成SVG文件,而是先组成字符串然后生成SVG文件。以下是源码,我做的时候是按照12组数据设计的,当数据少的话,柱子会很宽,我正在改进。 package com.hhh.Paint; import java.io.File; import java.io.FileOutputStream; import java.math.BigDecimal; /** * 该类是使用SVG画动态柱状图,只需调用本类的静态方法createSVG(String fileRealPath,String[] * pData,String[] pDataName)即可,我没有使用APACHE提供的SVG类库来生成SVG文件,只是将所有SVG描述用字符串返回 * * @author 顾国勇 * @version 1.0 */ public class PaintHistogram { /** * 正值所用的12种颜色 */ public static String[][] color = { { "#ff2222", "#ffaaaa", "#aa1111" }, { "#d0b83a", "#f2e692", "#9f8703" }, { "#ffdf00", "#fef195", "#ce9a31" }, { "#22FF22", "#aaffaa", "green" }, { "#ff9e00", "#ffbe08", "#bd7500" }, { "#799AE1", "#9aabEe", "#5778a1" }, { "#99c741", "#aef292", "#3e941b" }, { "#d0b83a", "#f2e692", "#9f8703" }, { "#319a00", "#66cc00", "#297110" }, { "#c27f34", "#d6a97b", "#82522b" }, { "#2222ff", "#aaaaff", "#1111aa" }, { "#ff2222", "#ffaaaa", "#aa1111" } }; //,{"#99c741","#aef292","#3e941b"} /** * 负值所用的颜色 */ public static String[][] colorNeg = { { "black", "#4F4F4F", "#757575" }, { "#1A0B0F", "#4F4F4F", "#757575" } }; /** * 存放转换成double型的数据,用于单例柱形 */ public static double[] data; /** * 存放转换成double型的数据,用于多例柱形 */ public static double[][] data1; /** * 数据总数,默认值为0 */ public static int dataNum = 0; /** * 默认柱状图X轴的起始X值 */ public static double HistogramXSx = 50.0; /** * 默认柱状图X轴的Y值 */ public static double HistogramXy = 440.0; /** * 将元数据从String转换成double,用于单例柱形 * * @param data * 字符串数组形式的元数据 * @return 转换好的数据 */ static double[] convertDataToDouble(String[] data) { double[] dData = new double[data.length]; dataNum = 0; for (int i = 0; i < data.length; i++) { if (data[i] != null && !data[i].equalsIgnoreCase("") && !data[i].equalsIgnoreCase(" ")) { dData[i] = Double.valueOf(data[i]).doubleValue(); dataNum += 1; } else { dData[i] = new Double(0.0).doubleValue(); dataNum += 1; } } return dData; } /** * 将元数据从String转换成double,用于多例柱形 * * @param data * 字符串数组形式的元数据 * @return 转换好的数据 */ static double[][] convertDataToDouble(String[][] data) { double[][] dData = new double[data.length][data[0].length]; dataNum = 0; for (int i = 0; i < data.length; i++) { for (int j = 0; j < data[i].length; j++) { if (data[i][j] != null && !data[i][j].equalsIgnoreCase("") && !data[i][j].equalsIgnoreCase(" ")) { dData[i][j] = Double.parseDouble(data[i][j]);//Double.valueOf(data[i][j]).doubleValue(); dataNum += 1; } else { dData[i][j] = new Double(0.0).doubleValue(); dataNum += 1; } } } return dData; } /** * @param fileRealPath * 指定的SVG文件的全路径,用于单例柱形 * @param pData * 元数据数组 * @param pDataName * 元数据数组名称 */ public static void createSVG(String fileRealPath, String[] pData, String[] pDataName) throws Exception { String sFile = paint(pData, pDataName); try { byte[] byteFil = sFile.getBytes("UTF-8"); File svgFile = new File(fileRealPath); if (svgFile.exists()) { svgFile.delete(); } FileOutputStream fos = new FileOutputStream(svgFile); fos.write(byteFil); fos.close(); } catch (Exception ex) { System.out.print(ex.getMessage()); } } /** * @param fileRealPath * 指定的SVG文件的全路径,用于多例柱形 * @param pData * 元数据数组 * @param pDataName * 元数据数组名称 */ public static void createSVG(String fileRealPath, String[][] pData, String[] pDataName) { try { String sFile = paint(pData, pDataName); byte[] byteFil = sFile.getBytes("UTF-8"); File svgFile = new File(fileRealPath); if (svgFile.exists()) { svgFile.delete(); } FileOutputStream fos = new FileOutputStream(svgFile); fos.write(byteFil); fos.close(); } catch (Exception ex) { System.out.print("createSVG:" + ex.getMessage()); } } /** * 根据原始数据过滤出最大最小值,已经考虑了数据正负的各种情况(全是正数,全是负数,有正有负),如果元数据中最大值或最小值的绝对值是小于1的,那最后返回的相应值的绝对值是1; * 如果最大或最小的绝对值小于100,那最后返回的相应值的绝对值是10的倍数;如果最大或最小的绝对值大于100,那最后返回的相应值的绝对值是50的倍数 * * @param data * <B>原始数据 </B> * @return <B>包含绝对值最大的值 </B> */ static double getValueMax(double[] pData) { //存放所有数据值 double max = pData[0]; double min = pData[0]; for (int i = 0; i < pData.length; i++) { if (pData[i] > max) max = pData[i]; if (pData[i] < min) min = pData[i]; } //全是负的 if (max <= 0 && min < 0) { min = Math.floor(min); double tMin = Math.abs(min); max = 0; if (tMin <= 1) min = -1; else { min = -(tMin < 100 ? Math.ceil(tMin / 10) * 10 : Math .ceil(tMin / 50) * 50); } } //有正有负 if (min < 0 && max > 0) { max = Math.ceil(max); if (max <= 1) max = 1; else { max = max < 100 ? Math.ceil(max / 10) * 10 : Math .ceil(max / 50) * 50; } min = Math.abs(Math.floor(min)); if (min <= 1) min = -1; else { min = -(min < 100 ? Math.ceil(min / 10) * 10 : Math .ceil(min / 50) * 50); } } //全是正的 if (min >= 0 && max >= 0) { if (max == 0.0) { max = min = 0.0; } else { if (max <= 1) max = 1; else { max = max < 100 ? Math.ceil(max / 10) * 10 : Math .ceil(max / 50) * 50; } } } double absMax = Math.abs(max) > Math.abs(min) ? Math.abs(max) : Math .abs(min); return absMax; } /** * 类初始化,主要是声明了文件开头和滤镜,对于画正负Y轴都通用 * * @return SVG文件头 */ static String initialize() { //文件头声明 StringBuffer sFile = new StringBuffer(); sFile.append("<?xml version='1.0' encoding='UTF-8'?>"); sFile.append("\n"); sFile .append("<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.0//EN' 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd'>"); sFile.append("\n"); sFile .append("<svg width='700' height='500' viewBox='0 0 700 500' xmlns='http://www.w3.org/2000/svg'>"); sFile.append("\n"); sFile.append("<desc>Histogram</desc>"); sFile.append("\n"); sFile .append("<rect x='0' y='0' width='700' height='500' fill='none' stroke='blue' stroke-width='1'/>"); sFile.append("\n"); //定义滤镜和箭头 sFile.append("<defs>"); sFile.append("\n"); sFile .append(" <marker id='Triangle' viewBox='0 0 10 10' refX='0' refY='5' markerUnits='strokeWidth' markerWidth='4' markerHeight='10' orient='auto'>"); sFile.append("\n"); sFile.append(" <path d='M 0 0 L 10 5 L 0 10 z'/>"); sFile.append("\n"); sFile.append(" </marker>"); sFile.append("\n"); sFile.append(" <linearGradient id='linear1'>"); sFile.append("\n"); sFile.append(" <stop offset='0%' stop-color='red'/>"); sFile.append("\n"); sFile.append(" <stop offset='30%' stop-color='white'/>"); sFile.append("\n"); sFile.append(" <stop offset='100%' stop-color='blue'/>"); sFile.append("\n"); sFile.append(" </linearGradient>"); sFile.append("\n"); sFile .append(" <filter id='MyFilter' filterUnits='userSpaceOnUse' x='0' y='0' width='100%' height='100%'>"); sFile.append("\n"); sFile .append(" <feGaussianBlur in='SourceAlpha' stdDeviation='3' result='blur'/>"); sFile.append("\n"); sFile .append(" <feOffset in='blur' dx='4' dy='4' result='offsetBlur'/>"); sFile.append("\n"); sFile.append("\n"); sFile .append(" <feSpecularLighting in='blur' surfaceScale='.5' specularConstant='2.5' specularExponent='128' lighting-color='#bbbbbb' result='specOut'>"); sFile.append("\n"); sFile.append(" <fePointLight x='-600' y='-100' z='2200'/>"); sFile.append("\n"); sFile.append(" </feSpecularLighting>"); sFile.append("\n"); sFile .append(" <feComposite in='specOut' in2='SourceAlpha' operator='in' result='specOut'/>"); sFile.append("\n"); sFile .append(" <feComposite in='SourceGraphic' in2='specOut' operator='arithmetic' k1='0' k2='1' k3='1' k4='0' result='litPaint'/>"); sFile.append("\n"); sFile.append(" <feMerge>"); sFile.append("\n"); sFile.append(" <feMergeNode in='offsetBlur'/>"); sFile.append("\n"); sFile.append(" <feMergeNode in='litPaint'/>"); sFile.append("\n"); sFile.append(" </feMerge>"); sFile.append("\n"); sFile.append(" </filter>"); sFile.append("\n"); sFile.append("<script type=\"text/javascript\">"); sFile.append("\n"); sFile.append("<![CDATA["); sFile.append("\n"); sFile.append(" function showValue(value) {"); sFile.append("\n"); sFile.append(" alert(value);"); sFile.append("\n"); sFile.append(" }"); sFile.append("\n"); sFile.append("]]>"); sFile.append("\n"); sFile.append("</script>"); sFile.append("\n"); sFile.append("</defs>"); sFile.append("\n"); //X轴 sFile .append("<line x1='50' y1='440' x2='660' y2='440' stroke-width='2' stroke-opacity='.4' fill='blue' stroke='#333300' />"); sFile.append("\n"); //Y轴 sFile .append("<line x1='50' y1='440' x2='50' y2='40' stroke-width='2' stroke-opacity='.4' fill='blue' stroke='#333300' />"); sFile.append("\n"); sFile .append("<line x1='60' y1='30' x2='60' y2='430' stroke-width='0.5' stroke='#333300'/>"); /* Y轴刻度线 */ for (int i = 0; i < 11; i++) { double tempY1 = 40 + i * 40.0; //每根Y轴刻度线的起始位置 sFile.append("<line x1='50' y1='" + tempY1 + "' x2='60' y2='" + (tempY1 - 10) + "' stroke-width='1' stroke='#333300'/>"); sFile.append("<line x1='60' y1='" + (tempY1 - 10) + "' x2='670' y2='" + (tempY1 - 10) + "' stroke='#333300'/>"); sFile.append("\n"); // sFile.append("<polygon points='50," + tempY1 + " 660," + tempY1 // + " 670," + (tempY1 - 10) + " 60," + (tempY1 - 10) // + " ' "); // sFile // .append(" // style='fill:rgb(37,170,219);stroke:rgb(37,170,219);stroke-width:1;fill-opacity:0.3;stroke-opacity:0;opacity:0.8'/>"); // sFile.append("\n"); } return sFile.toString(); } /** * @param args */ public static void main(String[] args) { try { String[][] data = { { "54", "44", "122" }, { "", "12", "-468" }, { "78", "520", "10" } }; String[] data1 = { "54", "4424.225", "12200" ,"-4658" }; String[] dataName = { "营业所一", "二二二二", "III" }; PaintHistogram.createSVG("d:\\t1.svg", data, dataName); PaintHistogram.createSVG("d:\\t2.svg", data1, dataName); } catch (Exception ex) { System.out.print(ex.getMessage()); } } /** * 画出柱状及刻度等,适用于X轴每格都只显示一个柱型 * * @param pData * 元数据 * @param pDataName * 元数据的名称 */ static String paint(String[] pData, String[] pDataName) throws Exception { StringBuffer sFile = new StringBuffer(); data = convertDataToDouble(pData); double valueMM = getValueMax(data); //取得最大值 sFile.append(initialize()); //X Y步长 double yStep = 400.0 / valueMM; double xStep = (new BigDecimal(600 / pDataName.length).setScale(3, BigDecimal.ROUND_HALF_UP)).doubleValue(); double colWidth = xStep / 2; //柱型的宽度 double colWidthPre = xStep / 4; //每个单元格起始与柱型的距离 double colWidthNext = xStep / 4; //每个单元格结束与柱型的距离 /* x轴刻度线 */ for (int i = 0; i < pDataName.length; i++) { double tempX1 = i == 0 ? HistogramXSx + i * xStep : HistogramXSx + i * xStep + 10; //每根X轴刻度线的起始位置 //double tempX2 = tempX1 + colWidthPre; //每根柱状的起始位置 sFile.append("<line x1='" + tempX1 + "' y1='" + HistogramXy + "' x2='" + (tempX1 + 10.0) + "' y2='" + (HistogramXy - 10) + "' stroke-width='1' stroke='#333300'/>"); sFile.append("\n"); } /* 画Y轴刻度值 */ int maxValue = (int) valueMM; int valueX; //Y轴刻度值起始的X坐标 String fontsize = ""; if (maxValue > 10000) { valueX = 5; fontsize = "11px"; } else { valueX = 15; fontsize = "13px"; } for (int i = 0; i < 10; i++) { sFile.append("<text x='" + valueX + "' y='" + (45 + i * 40) + "' fill='black' font-family='Verdana' font-size='" + fontsize + "'>"); sFile.append("\n"); sFile.append((maxValue / 10) * (10 - i)); sFile.append("\n"); sFile.append("</text>"); sFile.append("\n"); } /* 画矩形 */ sFile.append("<g style='filter:url(#MyFilter)'>"); sFile.append("\n"); for (int i = 0; i < pDataName.length; i++) { double tempX1 = i == 0 ? HistogramXSx + i * xStep : HistogramXSx + i * xStep + 10; //每根X轴刻度线的起始位置 double tempX2 = tempX1 + colWidthPre; //每根柱状的起始位置 double colHeight = Math.ceil(Math.abs(data[i]) * yStep); double tempY = 440 - colHeight; String[] colorHistogram; //三个柱面的颜色 if (data[i] < 0) { colorHistogram = colorNeg[0]; sFile.append(paintHistogram(colWidth, colHeight, tempX2, tempY, colorHistogram, data[i])); } else if (data[i] > 0) { int n = i % color.length; colorHistogram = color[n]; sFile.append(paintHistogram(colWidth, colHeight, tempX2, tempY, colorHistogram, data[i])); } /* 添加X轴字段说明 字无法在一行内显示的话,需要换行 */ int n = (int) xStep / 13;//每行显示几个字符 int m = pDataName[i].length() % n == 0 ? pDataName[i].length() / n : (int) (pDataName[i].length() / n + 1);//总共显示几行 int l = 0;//记录总共处理了多少字符 if (m > 1) { sFile .append("<text fill='blue' font-size='15px' font-family='STFangsong'>"); for (int j = 0; j < m; j++) { for (int z = 0; z < n; z++) { l = j * n + z; if (l == pDataName[i].length()) { break; } sFile.append("<tspan x='" + (tempX1 + z * 15) + "' y='" + (460 + j * 20) + "' >"); sFile.append(pDataName[i].substring(l, l + 1)); sFile.append("</tspan>"); } if (l == pDataName[i].length()) { break; } } sFile.append("</text>"); sFile.append("\n"); } else { sFile .append("<text x='" + (5 + tempX1) + "' y='460' fill='blue' font-size='15px' font-family='STFangsong'>" + pDataName[i] + "</text>"); sFile.append("\n"); } } sFile.append("</g>"); sFile.append("\n"); sFile.append("</svg>"); return sFile.toString(); } /** * 画出柱状及刻度等,适用于X轴每格都显示多个柱型 * * @param pData * 具体的元数据 * @param pDataName * 元数据的名称 */ static String paint(String[][] pData, String[] pDataName) { StringBuffer sFile = new StringBuffer(); data1 = convertDataToDouble(pData); //取得最大值 double[] tempValue = new double[dataNum]; for (int i = 0; i < data1.length; i++) { for (int j = 0; j < data1[i].length; j++) { tempValue[i * data1[i].length + j] = data1[i][j]; } } double valueMM = getValueMax(tempValue); sFile.append(initialize()); double yStep = 400.0 / valueMM; double xStep = (new BigDecimal(600 / pDataName.length).setScale(3, BigDecimal.ROUND_HALF_UP)).doubleValue(); double colWidthSum = xStep / 2; //多个柱型的宽度之和 double colWidthPre = xStep / 4; //每个单元格起始与柱形的距离 double colWidthNext = xStep / 4; //每个单元格结束与柱形的距离 double colWidth = colWidthSum / data1[0].length; //单个柱形的宽度 /* x轴刻度线 */ for (int i = 0; i < pDataName.length; i++) { double tempX1 = i == 0 ? HistogramXSx + i * xStep : HistogramXSx + i * xStep + 10; //每根X轴刻度线的起始位置 //double tempX2 = tempX1 + colWidthPre; //每根柱形的起始位置 sFile.append("<line x1='" + tempX1 + "' y1='" + HistogramXy + "' x2='" + (tempX1 + 10.0) + "' y2='" + (HistogramXy - 10) + "' stroke-width='1' stroke='#333300'/>"); sFile.append("\n"); } /* 画Y轴刻度值 */ int maxValue = (int) valueMM; int valueX;//Y轴刻度值起始的X坐标 String fontsize = ""; if (maxValue > 10000) { valueX = 5; fontsize = "11px"; } else { valueX = 15; fontsize = "13px"; } for (int i = 0; i < 10; i++) { sFile.append("<text x='" + valueX + "' y='" + (45 + i * 40) + "' fill='black' font-family='Verdana' font-size='" + fontsize + "'>"); sFile.append("\n"); sFile.append((maxValue / 10) * (10 - i)); sFile.append("\n"); sFile.append("</text>"); sFile.append("\n"); } /* 画矩形 */ sFile.append("<g style='filter:url(#MyFilter)'>"); sFile.append("\n"); for (int i = 0; i < pDataName.length; i++) { //每根X轴刻度线的起始位置 double tempX1 = i == 0 ? HistogramXSx + i * xStep : HistogramXSx + i * xStep + 10; for (int j = 0; j < data1[i].length; j++) { double tempX2 = tempX1 + colWidthPre + j * colWidth; //每根柱形的起始位置 double colHeight = Math.ceil(Math.abs(data1[i][j]) * yStep); double tempY = 440 - colHeight; String[] colorHistogram; //三个柱面的颜色 if (data1[i][j] < 0) { colorHistogram = colorNeg[0]; sFile.append(paintHistogram(colWidth, colHeight, tempX2, tempY, colorHistogram, data1[i][j])); } else if (data1[i][j] > 0) { int n = j % color.length; colorHistogram = color[n]; sFile.append(paintHistogram(colWidth, colHeight, tempX2, tempY, colorHistogram, data1[i][j])); } } /* 添加X轴字段说明 字无法在一行内显示的话,需要换行 */ int n = (int) xStep / 13;//每行显示几个字符 int m = pDataName[i].length() % n == 0 ? pDataName[i].length() / n : (int) (pDataName[i].length() / n + 1);//总共显示几行 int l = 0;//记录总共处理了多少字符 if (m > 1) { sFile .append("<text fill='blue' font-size='15px' font-family='STFangsong'>"); for (int j = 0; j < m; j++) { for (int z = 0; z < n; z++) { l = j * n + z; if (l == pDataName[i].length()) { break; } sFile.append("<tspan x='" + (tempX1 + z * 15) + "' y='" + (460 + j * 20) + "' >"); sFile.append(pDataName[i].substring(l, l + 1)); sFile.append("</tspan>"); } if (l == pDataName[i].length()) { break; } } sFile.append("</text>"); sFile.append("\n"); } else { sFile .append("<text x='" + (5 + tempX1) + "' y='460' fill='blue' font-size='15px' font-family='STFangsong'>" + pDataName[i] + "</text>"); sFile.append("\n"); } } sFile.append("</g>"); sFile.append("\n"); sFile.append("</svg>"); return sFile.toString(); } /** * 画单一柱形 * * @param colWidth * 柱形宽度 * @param colHeight * 柱形高度 * @param x * 柱形X值 * @param y * 柱形Y值 * @param color * 柱形三个面的颜色 * @param dataValue * 柱形代表的值 * @return SVG字符串 */ static String paintHistogram(double colWidth, double colHeight, double x, double y, String[] color, double dataValue) { StringBuffer sFile = new StringBuffer(); double dOffset = (colWidth / 3) > 10.0 ? 10.0 : (colWidth / 3);//斜面的偏移量 sFile.append("<rect width='" + colWidth + "' height='" + colHeight + "' x='" + x + "' y='" + y + "' fill='" + color[0] + "' onclick='showValue(" + dataValue + ")'/>"); sFile.append("\n"); sFile.append("<polygon points='" + x + "," + y + " " + (x + dOffset) + "," + (y - dOffset) + " " + (x + dOffset + colWidth) + "," + (y - dOffset) + " " + (x + colWidth) + "," + y + "' style='fill:" + color[1] + ";stroke:" + color[1] + ";stroke-width:1;' onclick='showValue(" + dataValue + ")'/>"); sFile.append("\n"); sFile.append("<polygon points='" + (x + dOffset + colWidth) + "," + (y - dOffset) + " " + (x + colWidth) + "," + y + " " + (x + colWidth) + "," + 440 + " " + (x + dOffset + colWidth) + "," + (y - dOffset + colHeight) + "' style='fill:" + color[2] + ";stroke:" + color[2] + ";stroke-width:1;' onclick='showValue(" + dataValue + ")'/>"); return sFile.toString(); } } 作者Blog:http://blog.csdn.net/turing_gu/ http://dev.csdn.net/article/62/article/35/35577.shtm
|