首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 生成 SVG 图表实战:手写饼图/3D 饼图 + JFreeChart + Batik 导出

     編輯:Java 相關知識

SVG 作为矢量图形格式,具备**无损缩放、文本可搜索、DOM 可操作、体积小**等优势,适合 Web 报表、打印、响应式大屏。本文提供两条路径:手写 SVG 路径生成饼图/3D 饼图 (零依赖、极致可控) 与 JFreeChart + Apache Batik 导出 SVG (功能全、标准兼容),附完整代码、中文渲染方案与选型建议。

方案一:手写 SVG — 零依赖、全掌控、极致轻量

核心几何:饼图扇形 path 计算

SVG ``:

  • M cx cy:移动到圆心。
  • L cx+r cy:画线到起始点 (0° 方向)。
  • A r r 0 largeArcFlag 1 x y:椭圆弧,largeArcFlag = 角度 > 180° ? 1 : 0。
  • Z:闭合回圆心。

完整 Java 生成器 (含图例、百分比标签、引导线)


public class SvgPieChartGenerator {
    private static final String[] PALETTE = {
        "#4E79A7", "#F28E2B", "#E15759", "#76B7B2", "#59A14F",
        "#EDC949", "#B07AA1", "#FF9DA7", "#9C755F", "#BAB0AC"
    };

    public static String generate(double[] values, String[] labels, int width, int height) {
        double sum = Arrays.stream(values).sum();
        double[] percents = Arrays.stream(values).map(v -> v / sum * 100).toArray();

        int cx = width / 2;
        int cy = height / 2;
        int radius = Math.min(cx, cy) - 20;
        int legendX = cx + radius + 30;
        int legendY = cy - radius;

        StringBuilder svg = new StringBuilder();
        svg.append(String.format("\n"));
        svg.append(String.format("\n", width, height, width, height));
        svg.append("\n");

        double startAngle = 0; // 度
        for (int i = 0; i < values.length; i++) {
            double percent = percents[i];
            double angle = percent / 100 * 360;
            double endAngle = startAngle + angle;
            int largeArc = angle > 180 ? 1 : 0;

            // 计算弧终点
            double radStart = Math.toRadians(startAngle - 90); // SVG 0° 在 3 点钟,-90° 对齐 12 点钟
            double radEnd = Math.toRadians(endAngle - 90);
            double x1 = cx + radius * Math.cos(radStart);
            double y1 = cy + radius * Math.sin(radStart);
            double x2 = cx + radius * Math.cos(radEnd);
            double y2 = cy + radius * Math.sin(radEnd);

            // 扇形 path
            svg.append(String.format("\n",
                    cx, cy, x1, y1, radius, radius, largeArc, x2, y2, PALETTE[i % PALETTE.length]));

            // 百分比标签 + 引导线 (中间角度)
            double midAngle = startAngle + angle / 2;
            double radMid = Math.toRadians(midAngle - 90);
            double labelR = radius * 0.7;
            double lx = cx + labelR * Math.cos(radMid);
            double ly = cy + labelR * Math.sin(radMid);
            svg.append(String.format("%.1f%%\n",
                    lx, ly, percent));

            // 图例
            int lyPos = legendY + i * 22;
            svg.append(String.format("\n", legendX, lyPos, PALETTE[i % PALETTE.length]));
            svg.append(String.format("%s (%.1f%%)\n",
                    legendX + 20, lyPos + 7, labels[i], percent));

            startAngle = endAngle;
        }

        svg.append("");
        return svg.toString();
    }
}

3D 饼图原理 (手写版)

3D 效果 = **顶面椭圆 + 侧面路径 + 底面椭圆** (或仅顶面 + 侧面)。核心参数:

  • rx (水平半轴)、ry (垂直半轴,ry < rx 形成透视)。
  • depth (厚度),侧面高度。
  • 每个扇形生成 3 个 ``:顶面、侧面 (两条垂直线 + 底部弧)、底面 (可选)。
  • 颜色深浅区分:顶面亮、侧面暗。

代码量较大,建议直接用 JFreeChart 3D 渲染器再导出 SVG。

方案二:JFreeChart + Batik — 标准库、功能全、生产就绪

Maven 依赖


<dependencies>
  <dependency><groupId>org.jfree</groupId><artifactId>jfreechart</artifactId><version>1.5.4</version></dependency>
  <dependency><groupId>org.apache.xmlgraphics</groupId><artifactId>batik-transcoder</artifactId><version>1.16</version></dependency>
  <!-- 中文字体支持:确保服务器安装 Microsoft YaHei / SimHei / Noto Sans CJK -->
</dependencies>

完整导出代码 (柱状图/饼图/折线图通用)


public class JFreeChartSvgExporter {
    public static void exportPieChart(String filePath, String title, String[] keys, double[] values) throws IOException {
        // 1. 构建数据集
        DefaultPieDataset dataset = new DefaultPieDataset();
        for (int i = 0; i < keys.length; i++) dataset.setValue(keys[i], values[i]);

        // 2. 创建图表
        JFreeChart chart = ChartFactory.createPieChart(
            title, dataset, true, true, false
        );
        // 中文标题/图例字体
        chart.getTitle().setFont(new Font("Microsoft YaHei", Font.BOLD, 18));
        chart.getLegend().setItemFont(new Font("Microsoft YaHei", Font.PLAIN, 12));
        // 避免饼图标签重叠
        PiePlot plot = (PiePlot) chart.getPlot();
        plot.setLabelFont(new Font("Microsoft YaHei", Font.PLAIN, 11));
        plot.setSimpleLabels(true); // 仅显示 {0}={1}

        // 3. 导出 SVG
        int width = 800, height = 600;
        Rectangle bounds = new Rectangle(0, 0, width, height);
        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
        Document document = domImpl.createDocument(null, "svg", null);
        SVGGraphics2D g2 = new SVGGraphics2D(document);
        chart.draw(g2, bounds);
        
        try (OutputStream os = new FileOutputStream(filePath);
             Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
            g2.stream(writer, true); // true = use CSS
        }
    }

    public static void exportBarChart(String filePath, String title, String categoryLabel, String valueLabel,
                                      String[] seriesKeys, String[] categoryKeys, double[][] data) throws IOException {
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        for (int s = 0; s < seriesKeys.length; s++) {
            for (int c = 0; c < categoryKeys.length; c++) {
                dataset.addValue(data[s][c], seriesKeys[s], categoryKeys[c]);
            }
        }
        JFreeChart chart = ChartFactory.createBarChart3D(title, categoryLabel, valueLabel, dataset,
                PlotOrientation.VERTICAL, true, true, false);
        // 字体设置同饼图
        exportChartToSvg(chart, filePath, 800, 600);
    }

    private static void exportChartToSvg(JFreeChart chart, String filePath, int w, int h) throws IOException {
        Rectangle bounds = new Rectangle(0, 0, w, h);
        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
        Document document = domImpl.createDocument(null, "svg", null);
        SVGGraphics2D g2 = new SVGGraphics2D(document);
        chart.draw(g2, bounds);
        try (OutputStream os = new FileOutputStream(filePath);
             Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
            g2.stream(writer, true);
        }
    }
}

中文渲染关键:字体配置

  • **Linux 服务器必须安装中文字体**:apt-get install fonts-noto-cjk 或 yum install google-noto-sans-cjk-fonts。
  • JFreeChart 通过 Font 对象指定字体名,需与系统安装字体名一致 (Noto Sans CJK SC / Microsoft YaHei / SimHei)。
  • Batik 导出 SVG 时会将字体名写入 ``,**浏览器端也需有该字体**才能正确显示;或用 `` 嵌入 WOFF2 (进阶)。

方案对比与选型建议

| 维度 | 手写 SVG | JFreeChart + Batik | |------|----------|---------------------| | 依赖大小 | **0** (纯 JDK) | ~5 MB (jfreechart + batik + 传递依赖) | | 图表类型 | 饼图、简单柱图、折线图 | **全系列**:饼/环/柱/线/散点/雷达/甘特/组合/3D | | 交互/动画 | 需前端 JS 实现 | 静态 SVG,前端可配合 JS 增强 | | 中文支持 | 手动指定 font-family | 统一 Font API,需服务器装字体 | | 性能 | **极快** (字符串拼接) | 中等 (对象模型 + 渲染管线) | | 维护成本 | 低 (几百行自控代码) | 低 (成熟库,配置即用) | | 推荐场景 | **微服务/Serverless/嵌入式/极简饼图** | **企业级报表/多图表类型/标准合规/导出 PDF/PNG 复用** |

进阶:SVG 优化与前端集成

  1. 压缩:gzip / brotli,SVG 文本压缩率极高 (通常 > 80%)。
  2. 精简:移除 `` 未用定义、合并相同样式、缩短小数位 (JS SVGO / svgo-loader)。
  3. 响应式:SVG 根元素加 width="100%" height="100%" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet"。
  4. 前端注入:<div id="chart"></div> + fetch('/api/chart.svg').then(r=>r.text()).then(svg=>document.getElementById('chart').innerHTML=svg)。
  5. 交互增强:鼠标悬停高亮扇形、点击钻取、Tooltip (D3.js / ECharts SVG 渲染器)。

总结

  • **只需极简饼图/柱图、追求零依赖/极致性能/Serverless 部署** → **手写 SVG** (几百行代码完全可控)。
  • **需要专业图表库、多类型图表、标准合规、后续可能导出 PDF/PNG/打印** → **JFreeChart + Batik** (成熟生态、配置即用)。
  • **中文渲染核心** = 服务器安装 CJK 字体 + 代码显式指定 Font + 前端可用字体回退。

掌握双路径,即能在「极简内部工具」与「企业级报表系统」间游刃有余地交付矢量图表能力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved