Java使用jfreechart生成pdf图表

本项目主要技术是jfreechart和itextpdf,纯Java生成pdf图表,我给大家分享一下完成过程中遇到的各种坑.本项目适合于需要生成pdf图表又没有html模板的需求.

我尝试过freemarker,失败在模板制作上.使用word制作模板然后转成html,再使用freemarker根据html模板生成pdf,但word只能制作带表格和文本的模板,折线图这种图表只能作为图片放在模板里,这样就不能插入数据,故而失败.哪位同学知道如何制作图表模板的请不吝赐教.

还尝试过 abel533 / ECharts,我想到每次生成图表的需求可能不一样,所以想到从echarts拿到这些图表直接用,但这个我没能生成,哪位同学想试试的可以试一下

http://hzhcontrols.com/new-566194.html

还尝试过使用js的方式,找前端朋友写一个只有表格和文本的js,测试一下可以导出成pdf,然后将echarts的图表导出为js,嵌入第一个js里,再导出就发现pdf里echarts的位置是一片空白.

给大家提供一个根据html生成pdf的短小精悍的demo:

 
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
 
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import com.lowagie.text.pdf.BaseFont;
 
public class aaa {
 
 
    /**
     * @param args
     */
 
    public static void main(String[] args) throws Exception {
        // Todo Auto-generated method stub
        String inputFile = "src/main/resources/pdfPage.html";
        String url = new File(inputFile).toURI().toURL().toString();
        String outputFile = "D://test.pdf";
        System.out.println(url);
        OutputStream os = new FileOutputStream(outputFile);
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocument(url); // 解决中文支持问题
        ITextFontResolver fontResolver = renderer.getFontResolver();
        fontResolver.addFont("C:/Windows/Fonts/SimsUN.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // 解决图片的相对路径问题
        renderer.getSharedContext().setBaseURL("file:/D:/z/temp/");
        renderer.layout();
        renderer.createPDF(os);
        os.close();
    }
}

最后找到了一个用无界浏览器生成的方法,相当于是在java里搭建一个浏览器,然后把页面整个画好,包括图表表格等等,然后把整个页面导出为图片,再转为pdf,整个过程很复杂,感兴趣的同学来找我要吧

正文开始~

开始之前先放一个整体效果

首先是依赖

 <!--用于jfreechart生成图片  -->
        <dependency>
            <groupId>org.jfree</groupId>
            <artifactId>jfreechart</artifactId>
            <version>1.5.0</version>
        </dependency>
        <!-- 应该是亚洲字体支持 -->
        <!-- 一定要加,否则会出现字体找不到错误  -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <!-- 中文字体支持 -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>font-asian</artifactId>
            <version>7.1.13</version>
        </dependency>
        <!--用于生成pdf-->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.4</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

然后是测试类

import com.example.util.CreatePdf;
import com.example.util.Datas;
import com.example.dto.TableValue;
import com.itextpdf.text.DocumentException;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
 
@SpringBootTest
class Test1ApplicationTests {
 
    @Test
    void GeneratePdf() throws IOException, DocumentException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        TableValue tableValue = Datas.formData();
//        CreateEchartsPdfUtils.createTable(outputStream,tableContentList,tableContentList2,createLinePorts(), tableContentList3,createPiePort(),tableContentList4,createLinePort(),createBarChartsPort(),createBarChartPort(),createStackedBarChartsPort());
        CreatePdf.createPdf(outputStream,tableValue);
        FileUtils.copyInputStreamToFile(new ByteArrayInputStream(outputStream.toByteArray()),new File("D:\\折线图/demo2.pdf"));
 
    }

先来个简单表格试试水

1.表格

数据源

import com.example.dto.TableValue;
 
public class Datas {
 
    public static TableValue formData() {
 
        TableValue tableValue = new TableValue();
        tableValue.setIndexValue1("1");
        tableValue.setIndexValue2("2");
        tableValue.setIndexValue3("3");
        tableValue.setIndexValue4("4");
        return tableValue;
    }

主要框架

import com.example.dto.TableValue;
import com.example.dto.ThreeLineColor;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfWriter;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
 
public class CreatePdf {
 
 
    public static void createPdf(ByteArrayOutputStream outputStream,
                                 TableValue tableValue) throws DocumentException, IOException {
        // 创建一个文档(默认大小A4,边距36, 36, 36, 36)
        Document document = new Document();
        // 设置文档大小
        document.setPageSize(PageSize.A4);
        // 设置边距,单位都是像素,换算大约1厘米=28.33像素
        document.setMargins(50, 50, 50, 50);
        // 创建writer,通过writer将文档写入磁盘
        PdfWriter writer = PdfWriter.getInstance(document, outputStream);
 
        // 打开文档,只有打开后才能往里面加东西
        document.open();
        // 设置作者
        document.addAuthor("作者");
        // 设置创建者
        document.addCreator("创建者");
 
 
        //设置封面图片
        Image image = Image.getInstance("src/main/resources/static/萌猫.jpg");
        image.setAlignment(Image.ALIGN_LEFT);
        image.scaleAbsolute(500, 300);
        document.add(image);
 
        //设置标题
        document.add(CreateEchartsPdfUtils.createHead2("生成pdf图表demo", Font.NORMAL, Element.ALIGN_CENTER));
        
        document.add(CreateEchartsPdfUtils.createHead3("01  表格", Font.BOLD, Element.ALIGN_LEFT));
        //第一个表格
        oneForm(document, tableValue);
        document.close();
        writer.close();
    }
 
    /**
     * 文件传输量表格
     *
     * @param
     * @return
     */
    private static void oneForm(Document document, TableValue tableValue) throws DocumentException, IOException {
 
        String[] oneForm = {"抖音", "微博", "虎牙", "小红书"};
        CreateEchartsPdfUtils.Form(document, tableValue, 4, oneForm, ThreeLineColor.danhui);
 
    }
 
}

多行表格的话传入list即可

输出pdf

需要注意的是,表格里的列数假如设置的是4列,数据源如果不足4个的话,整行就会不显示

2.日历图

日历图其实也是表格,只不过把表格的框线全部隐藏了,并添加了不同的背景颜色

 
    /**
     * 创建表格内容
     *
     * @param table            表格对象
     * @param tableContentList 表格内容对象
     */
    private static void createTableTwoCellCalendar(PdfPTable table, List<TableValue> tableContentList, BaseColor baseColor) throws DocumentException, IOException {
        if (ObjectUtils.isEmpty(tableContentList)) {
            return;
        }
        int j = 0;
        for (int i = 0; i < tableContentList.size(); i++) {
 
            TableValue TableValue = tableContentList.get(i);
            String date = TableValue.getIndexValue1();
            if (StringUtils.isBlank(date)) {
                return;
            }
            String[] split = date.split("-");
            //计算每月1号是星期几.以周四为例,返回四
            int weeksOfDate = DateUtils.getWeeksOfDate(Integer.valueOf(split[0]), Integer.valueOf(split[1]), 1);
            //返回4 前面就空4格  返回0就不空
            if (j < weeksOfDate) {
                table.addCell(createTableContentCalendar("", ""));
                j++;
                i--;
                continue;
            }
 
 
            //获取设置表格内容的字体与内容
            if (i % 2 != 0) {
                table.addCell(createTableContentCalendar(split[2], TableValue.getIndexValue2(), TableValue.getIndexValue3(), baseColor));
 
            } else {
                table.addCell(createTableContentCalendar(split[2], TableValue.getIndexValue2(), TableValue.getIndexValue3()));
            }
        }
        //一行内如果数据不足列数就不显示,为了让他显示,特地写了6个空字符串
        table.addCell(createTableContentCalendar("", ""));
        table.addCell(createTableContentCalendar("", ""));
        table.addCell(createTableContentCalendar("", ""));
        table.addCell(createTableContentCalendar("", ""));
        table.addCell(createTableContentCalendar("", ""));
        table.addCell(createTableContentCalendar("", ""));
 
    }
3.折线图
/**
     * 生成折线图
     *
     * @param datas 数据
     * @param xName X轴
     * @param yName Y轴
     * @return 生成的折线图byte数组
     */
    public static void createLinePortImage(Document document, Map<String, Map<String, BigDecimal>> datas, String xName, String yName) throws DocumentException, IOException {
        try {
            //种类数据集
            DefaultCategoryDataset dataset = new DefaultCategoryDataset();
            //遍历map
            for (Map.Entry<String, Map<String, BigDecimal>> entry : datas.entrySet()) {
                String key = entry.getKey();
                Map<String, BigDecimal> value = entry.getValue();
                for (Map.Entry<String, BigDecimal> decimalEntry : value.entrySet()) {
                    dataset.setValue(decimalEntry.getValue(),
                            key,
                            decimalEntry.getKey());
                }
            }
            //创建2D折线图,折线图分水平显示和垂直显示两种
            //legend:是否显示图例(对于简单的柱状图必须是false)
            //tooltips:是否生成工具
            //urls:是否生成URL链接
            JFreeChart chart = ChartFactory.createLineChart("", xName, yName, dataset,
                    PlotOrientation.VERTICAL,
                    true,
                    true,
                    true);
            //得到绘图区
            setLineRender((CategoryPlot) chart.getPlot(), true, true);
            ByteArrayOutputStream bas = new ByteArrayOutputStream();
            ChartUtils.writeChartAsJPEG(bas, 1.0f, chart, 800, 500, null);
            com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
            image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
            image.scaleAbsolute(500, 300);
            document.add(image);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 /**
     * 设置折线图样式
     *
     * @param plot             折线图对象
     * @param isShowDataLabels 是否显示数据标题
     * @param isShapesVisible  是否显示数据点
     */
    public static void setLineRender(CategoryPlot plot, boolean isShowDataLabels, boolean isShapesVisible) {
        plot.setNoDataMessage(NO_DATA_MSG);
        plot.setInsets(new RectangleInsets(10, 10, 0, 10), false);
        LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
        renderer.setDefaultStroke(new BasicStroke(1.5F));
        if (isShowDataLabels) {
            renderer.setDefaultItemLabelsVisible(true);
            renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator(StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING,
                    NumberFormat.getInstance()));
            renderer.setDefaultPositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE1, TextAnchor.BOTTOM_CENTER));
        }
        // 数据点绘制形状
        //显示数据点
        renderer.setDefaultShapesVisible(isShapesVisible);
 
//        renderer.setSeriesShapesVisible(0,isShapesVisible);
        //开启填充色
        renderer.setUseFillPaint(isShapesVisible);
        //数据点填充的颜色
        renderer.setSeriesFillPaint(0, Color.white);
        //开启外廓线(填充色开启后有效)
        renderer.setDrawOutlines(isShapesVisible);
        //使用外廓线颜色
        renderer.setUseOutlinePaint(isShapesVisible);
        //设置外框线颜色
//        renderer.setSeriesOutlinePaint(0,Color.yellow);
        //设置折线的颜色
        renderer.setSeriesPaint(0, ThreeLineColor.sGreen);
        renderer.setSeriesPaint(1, ThreeLineColor.zhaxianlan);
        renderer.setSeriesPaint(2, ThreeLineColor.zhexianlan);
        //设置外廓线大小
        renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0F));
        //设置线条粗细(setDefaultStroke和这个方法冲突)
        renderer.setSeriesStroke(0, new BasicStroke(1.0F));
 
 
        //圆形数据点
//        renderer.setSeriesShape(0, new Ellipse2D.Double(-2.0D,-2.0D,5.0D,5.0D) );
        plot.setRenderer(renderer);
        setXAixs(plot, CategoryLabelPositions.STANDARD);
        setYAixs(plot);
    }

x轴和y轴的设置

 /**
     * 设置类别图表(CategoryPlot) X坐标轴线条颜色和样式
     *
     * @param plot 类别图表对象  categoryLabelPositions  x轴文字倾斜度
     */
    public static void setXAixs(CategoryPlot plot, CategoryLabelPositions categoryLabelPositions) {
        Color lineColor = new Color(51, 51, 51);
        //STANDARD:x轴的字体倾斜度
        CategoryAxis axis = plot.getDomainAxis();
        axis.setCategoryLabelPositions(categoryLabelPositions);
        // X坐标轴颜色
        axis.setAxisLinePaint(lineColor);
        // X坐标轴标记|竖线颜色
        axis.setTickMarkPaint(lineColor);
        axis.setLabelFont(FONT);
        //      font = Font.createFont(Font.TRUETYPE_FONT,new FileInputStream("/usr/share/fonts/cjkuni-uming/uming.ttc"));
 
        axis.setTickLabelFont(FONT);
 
 
    }
 
    /**
     * 设置类别图表(CategoryPlot) Y坐标轴线条颜色和样式 同时防止数据无法显示
     *
     * @param plot 类别图表对象
     */
    public static void setYAixs(CategoryPlot plot) {
        Color axisColor = new Color(51, 51, 51);
        Color tickColor = new Color(219, 218, 218);
        ValueAxis axis = plot.getRangeAxis();
        // Y坐标轴颜色
        axis.setAxisLinePaint(axisColor);
        // Y坐标轴标记|竖线颜色
        axis.setTickMarkPaint(tickColor);
        // false隐藏Y刻度
        axis.setAxisLineVisible(true);
        axis.setTickMarksVisible(true);
        // Y轴网格线条
        plot.setRangeGridlinePaint(new Color(229, 229, 229));
        plot.setRangeGridlineStroke(new BasicStroke(1));
        // 设置顶部Y坐标轴间距,防止数据无法显示
        axis.setUpperMargin(0.1);
        // 设置底部Y坐标轴间距
        axis.setLowerMargin(0.1);
        axis.setLabelFont(FONT);
        axis.setTickLabelFont(FONT);
        //设置y最小取值范围.如果数据全是0,那么0这条折线将显示在y轴中间,y轴0刻度以下将用负数表示,
        // 但是是显示的是  0E0,-1E-9,-2E-9,-3E-9,-4E-9,这样用科学计数法表示的数,加上这个可
        // 以让y轴在数据为0的时候折线在0的水平位置,接近于x轴.但是y轴刻度仍任是科学计数法.不介意的话可以用
        //还可以设置最大值,这样的话y轴刻度就不会自动生成了,就是0-1000
    /*    axis.setLowerBound(0);
        axis.setUpperBound(1000);*/
 
    }

效果是这样子

大家有没有发现,x轴的字怎么模糊不清呢?是不是字体太小了呢?

放大也是这样,这事因为x轴的内容是 2023-01-01这样的日期,太长了.

将 setLineRender方法末尾的setXAixs(plot, CategoryLabelPositions.STANDARD)修改为CategoryLabelPositions.UP_45,将字体倾斜45度,效果如下

折线和数据点的颜色,数据点的形状等等都是可以自由调节的.

4.饼状图
/**
     * 生成饼图
     *
     * @param datas 数据
     * @return 返回生成的byte数组
     */
    public static void createPiePortImage(Document document, Map<String, BigDecimal> datas) throws DocumentException, IOException {
        try {
            // 如果不使用Font,中文将显示不出来
            DefaultPieDataset pds = new DefaultPieDataset();
            //遍历map
            for (Map.Entry<String, BigDecimal> entry : datas.entrySet()) {
                pds.setValue(entry.getKey(),
                        entry.getValue());
            }
            /**
             * 生成一个饼图的图表
             * 分别是:显示图表的标题、需要提供对应图表的DateSet对象、是否显示图例、是否生成贴士以及是否生成URL链接
             */
            JFreeChart chart = ChartFactory.createPieChart("", pds, true, true, true);
            setPieRender(chart.getPlot());
            ByteArrayOutputStream bas = new ByteArrayOutputStream();
            ChartUtils.writeChartAsJPEG(bas, 1.0f, chart, 800, 500, null);
            com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
            image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
            image.scaleAbsolute(500, 300);
            document.add(image);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
/**
     * 设置饼状图渲染
     *
     * @param plot 饼图对象
     */
    public static void setPieRender(Plot plot) {
 
        plot.setNoDataMessage(NO_DATA_MSG);
        plot.setInsets(new RectangleInsets(10, 10, 5, 10));
        PiePlot piePlot = (PiePlot) plot;
        piePlot.setInsets(new RectangleInsets(0, 0, 0, 0));
        // 圆形
        piePlot.setCircular(true);
        piePlot.setLabelGap(0.01);
        piePlot.setInteriorGap(0.05D);
        // 图例形状
        piePlot.setLegendItemShape(new Rectangle(10, 10));
        piePlot.setIgnoreNullValues(true);
        // 去掉背景色
        piePlot.setLabelBackgroundPaint(null);
        // 去掉阴影
        piePlot.setLabelShadowPaint(null);
        // 去掉边框
        piePlot.setLabelOutlinePaint(null);
        piePlot.setShadowPaint(null);
        // 0:category 1:value:2 :percentage
        // 显示标签数据 {0} 代表 饼状图分类的名字,{1} 显示具体数值,{2} 显示百分比
        //{0}:{1} 英雄联盟:20 {0}:{2} 英雄联盟 20%
        piePlot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0}:{1}"));
 
    }

饼状图挺简单的,需要注意的就是值的显示 {0}:{1}显示的就是

{0}:{2}显示的就是百分比

  1. 柱状图
 
    /**
     * 生成柱状图
     *
     * @param datas 数据
     * @return
     */
    public static void createBarChartImage(Document document, Map<String, Map<String, BigDecimal>> datas) throws DocumentException, IOException {
 
        try {
            //种类数据集
            DefaultCategoryDataset dataset = new DefaultCategoryDataset();
            //遍历map
            for (Map.Entry<String, Map<String, BigDecimal>> entry : datas.entrySet()) {
                String key = entry.getKey();
                Map<String, BigDecimal> value = entry.getValue();
                for (Map.Entry<String, BigDecimal> decimalEntry : value.entrySet()) {
                    dataset.setValue(decimalEntry.getValue(),
                            key,
                            decimalEntry.getKey());
                }
            }
            //legend:是否显示图例(对于简单的柱状图必须是false)
            //tooltips:是否生成工具
            //urls:是否生成URL链接
            JFreeChart chart = ChartFactory.createBarChart("", "", "", dataset,
                    PlotOrientation.VERTICAL,
                    true,
                    true,
                    false);
            //得到绘图区
            setBarChartRender((CategoryPlot) chart.getPlot(), true, true);
            ByteArrayOutputStream bas = new ByteArrayOutputStream();
            ChartUtils.writeChartAsJPEG(bas, 1.0f, chart, 800, 500, null);
            com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
            image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
            image.scaleAbsolute(500, 300);
            document.add(image);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 设置柱状图样式
     *
     * @param plot             折线图对象
     * @param isShowDataLabels 是否显示数据标题
     * @param isShapesVisible  是否显示数据点
     */
    public static void setBarChartRender(CategoryPlot plot, boolean isShowDataLabels, boolean isShapesVisible) {
        plot.setNoDataMessage(NO_DATA_MSG);
        plot.setInsets(new RectangleInsets(10, 10, 0, 10), false);
        BarRenderer renderer = (BarRenderer) plot.getRenderer();
        renderer.setDefaultStroke(new BasicStroke(0F));
        if (isShowDataLabels) {
            renderer.setDefaultItemLabelsVisible(true);
            renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator(StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING,
                    NumberFormat.getInstance()));
            renderer.setDefaultPositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_CENTER));
        }
        renderer.setDefaultSeriesVisible(true);
        //设置柱子的宽度
        renderer.setMaximumBarWidth(0.05);
        //设置柱子的高度
//        renderer.setMinimumBarLength(0.2);
        //设置柱子边框线可见
//        renderer.setDrawBarOutline(isShapesVisible);
        //设置柱子边框线的颜色
//        renderer.setSeriesOutlinePaint(0, Color.green);
 
        //设置每个地区所包含的平行柱的距离
//        renderer.setItemMargin(0.5);
        //显示每个柱的数值,并修改该数值的字体属性
        renderer.setIncludeBaseInRange(true);
        //柱子的透明度
        plot.setForegroundAlpha(1.0f);
        //柱子的文字偏离值
//        renderer.setItemLabelAnchorOffset(10D);
        //设置柱子的颜色
        renderer.setSeriesPaint(0, Color.yellow);
        //设置外廓线大小
/*        renderer.setSeriesOutlineStroke(0,new BasicStroke(2.0F));
        //设置线条粗细
        renderer.setSeriesStroke(0,new BasicStroke(2.0F));*/
 
 
        setXAixs(plot, CategoryLabelPositions.STANDARD);
        setYAixs(plot);
    }

柱子的颜色,透明度,都是可以设置的,字体也可以设置为柱体内,柱体上,柱体旁

6.堆叠柱状图
  JFreeChart chart = ChartFactory.createStackedBarChart("", "", "", dataset,
                    PlotOrientation.VERTICAL,
                    true,
                    true,
                    false);
            //得到绘图区

只需将ChartFactory的createBarChart改为createStackedBarChart就可以

7.柱状图和折线图的组合图

第一种是y轴分离,相当于两个图合在一起,共享x轴

/**
     * 生成柱状图混合折线图
     *  柱状图和折线图共享x轴,y轴是分隔开的,相当于是柱状图上面有个折线图.
     * @param datas 数据
     * @return
     */
    public static void createBarChartAndLineImage(Document document,  Map<String, Map<String, BigDecimal>> datas) throws DocumentException, IOException {
 
        try {
            //种类数据集
            DefaultCategoryDataset dataset = new DefaultCategoryDataset();
            //遍历map
            for (Map.Entry<String, Map<String, BigDecimal>> entry : datas.entrySet()) {
                String key = entry.getKey();
                Map<String, BigDecimal> value = entry.getValue();
                for (Map.Entry<String, BigDecimal> decimalEntry : value.entrySet()) {
                    dataset.setValue(decimalEntry.getValue(),
                            key,
                            decimalEntry.getKey());
                }
            }
 
 
            //创建数据源一的纵坐标对象。
            NumberAxis rangeAxis1 = new NumberAxis("Value");
            LineAndShapeRenderer renderer1 = new LineAndShapeRenderer();
//我们在这里将横坐标设置为NULL,表示不使用自己的横坐标对象,只使用自己的纵坐标和图表渲染对象(折线图)
            CategoryPlot subplot1 = new CategoryPlot(dataset, null, rangeAxis1,
                    renderer1);
 
            //纵坐标对象。
            NumberAxis rangeAxis2 = new NumberAxis("Value");
            //柱状图的图表渲染对象。
            BarRenderer renderer2 = new BarRenderer();
            //同样的不是用横坐标对象。
            CategoryPlot subplot2 = new CategoryPlot(dataset, null, rangeAxis2, renderer2);
            CategoryAxis domainAxis = new CategoryAxis("Category");
            //先合并横坐标。
            CombinedDomainCategoryPlot plot = new CombinedDomainCategoryPlot(
                    domainAxis);
            //添加纵坐标。
            plot.add(subplot1, 2);
            //添加纵坐标。
            plot.add(subplot2, 1);
            JFreeChart result = new JFreeChart(plot);
 
 
            ByteArrayOutputStream bas = new ByteArrayOutputStream();
            ChartUtils.writeChartAsJPEG(bas, 1.0f, result, 800, 500, null);
//            ChartUtils.writeChartAsJPEG(bas, 1.0f, line, 800, 500, null);
            com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
            image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
            image.scaleAbsolute(500, 300);
            document.add(image);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

第二种是双y轴,折线图和柱状图有交互的组合图

 /**
     * 生成柱状图混合折线图
     *双y轴,柱状图和折线图在一张图里
     * @param datas 数据
     * @return
     */
    public static void createChart(Document document,  Map<String, Map<String, BigDecimal>> datas) throws DocumentException, IOException {
       try {
            //种类数据集
            DefaultCategoryDataset dataset = new DefaultCategoryDataset();
            //遍历map
            for (Map.Entry<String, Map<String, BigDecimal>> entry : datas.entrySet()) {
                String key = entry.getKey();
                Map<String, BigDecimal> value = entry.getValue();
                for (Map.Entry<String, BigDecimal> decimalEntry : value.entrySet()) {
                    dataset.setValue(decimalEntry.getValue(),
                            key,
                            decimalEntry.getKey());
                }
            }
 
           // 初始化一个基础渲染规则为3D模式的柱状统计图效果的Chart图表。
           JFreeChart chart = ChartFactory.createBarChart("DoubleBarChart", "Category", "Value", dataset, PlotOrientation.VERTICAL, true, true, false);
 
           // 获取绘图区对象
            CategoryPlot plot = (CategoryPlot) chart.getPlot();
            // 设置轴1--数据匹配
            NumberAxis axis0 = new NumberAxis("第一条轴线");
            plot.setRangeAxis(0, axis0);
            plot.setDataset(0, dataset);
            plot.mapDatasetToRangeAxis(0, 0);
 
            // 重新生成一个图表渲染的对象(折线图渲染对象)。
            LineAndShapeRenderer lineandshaperenderer = new LineAndShapeRenderer();
 
 
            // 显示折点数据。
            lineandshaperenderer.setDefaultItemLabelsVisible(true);
            lineandshaperenderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());
 
 
            // 设置拐点是否可见/是否显示拐点
            lineandshaperenderer.setDefaultShapesVisible(true);
 
            // 设置线条是否被显示填充颜色
            lineandshaperenderer.setUseFillPaint(true);
 
 
            // 设置第一条折线的拐点颜色
            lineandshaperenderer.setSeriesFillPaint(0, Color.BLUE);
            // 设置第二条折线的拐点颜色
            lineandshaperenderer.setSeriesFillPaint(1, Color.RED);
 
 
            //设置折线颜色(第一条折线数据线)
            lineandshaperenderer.setSeriesPaint(0, new Color(91, 155, 213));
            //设置折线颜色(第二条折折线据线)
            lineandshaperenderer.setSeriesPaint(1, Color.RED);
 
 
            // 设置第一条折线的广度(粗细度)
            lineandshaperenderer.setSeriesStroke(0, new BasicStroke(1.8F));
 
            // 设置第二条折线的广度(粗细度)
            lineandshaperenderer.setSeriesStroke(1, new BasicStroke(1.8F));
 
 
            //设置拐点数值颜色,默认黑色
            lineandshaperenderer.setDefaultItemLabelsVisible(true); // 默认就是true,这里可以不用刻意声明。
            lineandshaperenderer.setDefaultItemLabelPaint(Color.BLUE);
 
 
            // 解决最高柱体或折点提示内容被遮盖的问题。
            lineandshaperenderer.setDefaultPositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_CENTER));
            lineandshaperenderer.setItemLabelAnchorOffset(2); // 设置柱形图上的文字偏离值
 
 
            // 重构第二个数据对象的渲染方式,由现在默认的Bar(柱状统计图)重构为刚刚初始化的Line(折线统计图)的渲染模式
            plot.setRenderer(1, lineandshaperenderer);
 
            // 设置轴2--数据匹配
            NumberAxis axis1 = new NumberAxis("第二条轴线");
            plot.setRangeAxis(1, axis1);
            plot.setDataset(1, dataset);
            plot.mapDatasetToRangeAxis(1, 1);
            plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);
 
            /** ---------------------- 中文乱码问题处理 Start ------------------------------- */
            CategoryAxis domainAxis = plot.getDomainAxis();     //水平底部列表
            domainAxis.setLabelFont(new Font("黑体", Font.BOLD, 14));     //水平底部标题
            domainAxis.setTickLabelFont(new Font("宋体", Font.BOLD, 12)); //垂直标题
 
            ValueAxis rangeAxis = plot.getRangeAxis();//获取柱状
            rangeAxis.setLabelFont(new Font("黑体", Font.BOLD, 15));
            chart.getLegend().setItemFont(new Font("黑体", Font.BOLD, 15));
            chart.getTitle().setFont(new Font("宋体", Font.BOLD, 20));//设置标题字体
            /** ---------------------- 中文乱码问题处理 End ------------------------------- */
 
 
            rangeAxis.setAutoRange(true);
 
 
            // 设置图表控件的背景颜色。
            chart.setBackgroundPaint(Color.WHITE);
            ByteArrayOutputStream bas = new ByteArrayOutputStream();
            ChartUtils.writeChartAsJPEG(bas, 1.0f, chart, 800, 500, null);
//            ChartUtils.writeChartAsJPEG(bas, 1.0f, line, 800, 500, null);
            com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
            image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
            image.scaleAbsolute(500, 300);
            document.add(image);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

更多组合形式请前往https://yveshe.blog.csdn.net/article/details/81560666?spm=1001.2101.3001.6650.4&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-4-81560666-blog-91975252.pc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-4-81560666-blog-91975252.pc_relevant_recovery_v2&utm_relevant_index=4

有这种的

tips1

每一个图表,我都是先写标题,再画图表

 
        document.add(CreateEchartsPdfUtils.createHead3("01  表格", Font.BOLD, Element.ALIGN_LEFT));
        //第一个表格
        oneForm(document, tableValue);
        //日历图
        document.add(CreateEchartsPdfUtils.createHead3("02  日历图", Font.BOLD, Element.ALIGN_LEFT));
        CreateEchartsPdfUtils.calendarTwoForm(document, calendar);
        //折线图
        document.add(CreateEchartsPdfUtils.createHead3("03  折线图", Font.BOLD, Element.ALIGN_LEFT));
        JFreeChartUtils.createLinePortImage(document,  linePorts, "日期", "数量");
        //饼状图
        document.add(CreateEchartsPdfUtils.createHead3("04  饼状图", Font.BOLD, Element.ALIGN_LEFT));
        JFreeChartUtils.createPiePortImage(document,  piePort);
        //柱状图
        document.add(CreateEchartsPdfUtils.createHead3("05  柱状图", Font.BOLD, Element.ALIGN_LEFT));
        JFreeChartUtils.createBarChartImage(document,  barChartPort);
        //堆叠柱状图
        document.add(CreateEchartsPdfUtils.createHead3("06  堆叠柱状图", Font.BOLD, Element.ALIGN_LEFT));
        JFreeChartUtils.createStackedBarChart(document,  stackedBarChartsPort);
        //柱状图和折线图组合图1
        document.add(CreateEchartsPdfUtils.createHead3("07  柱状图和折线图组合图1", Font.BOLD, Element.ALIGN_LEFT));
        JFreeChartUtils.createBarChartAndLineImage(document,  linePorts);
        //柱状图和折线图组合图2 双y轴
        document.add(CreateEchartsPdfUtils.createHead3("08  柱状图和折线图组合图2", Font.BOLD, Element.ALIGN_LEFT));
        JFreeChartUtils.createChart(document,  linePorts);

但是实际结果可能是这样的

这可能是jfreechart为了节省空间自动做的排版,遇到这样的情况大家可以先开发,开发完成后统一处理,可以在柱状图代码后加上document.newPage();

tips2

上linux测试环境进行测试后,遇到一个关于字体的问题

   private static Font createFont(int fontSize, int fontMode, BaseColor fontColor) throws DocumentException, IOException {
        BaseFont bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        return new Font(bfChinese, fontSize, fontMode, fontColor);
    }

这是创建标题时createHead3 所用的,Font是itext下的包,测试环境可以正常创建标题,正常显示

而折线图的x轴所用的字体是awt的字体

    private static final Font FONT = new Font("STSongStd-Light", Font.BOLD, 12);

awt的字体在测试环境找不到,所以自动找了个默认字体,贼丑,而且不清晰.

和itext的font相比,awt的font没有编码方式

只找到了itext的字体转awt字体的方法,没有找到awt转itext的方法

试过直接写字体文件名,写字体绝对路径,将字体转成流,都不行.最后将时间由2023-01-02格式改为日期 02,CategoryLabelPositions.UP_45修改为STANDARD,勉强再用.如果有懂的大佬指导下我应该怎么解决,如果你也遇到这个问题不知道怎么解决的话可以用我这个方法

tips3

大家生成pdf肯定都是调用好几个接口,等候接口返回数据再调下一个接口,整体响应时间太长,可以用CompletableFuture进行异步调用

原来取数据:

   @Test
    void GeneratePdf() throws IOException, DocumentException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        TableValue tableValue = Datas.formData();
 
        List<TableValue> calendar = Datas.calendar();
        Map<String, Map<String, BigDecimal>> linePorts = Datas.createLinePorts();
        Map<String, BigDecimal> piePort = Datas.createPiePort();
        Map<String, Map<String, BigDecimal>> barChartPort = Datas.createBarChartPort();
        Map<String, Map<String, BigDecimal>> stackedBarChartsPort = Datas.createStackedBarChartsPort();
//        CreateEchartsPdfUtils.createTable(outputStream,tableContentList,tableContentList2,createLinePorts(), tableContentList3,createPiePort(),tableContentList4,createLinePort(),createBarChartsPort(),createBarChartPort(),createStackedBarChartsPort());
        CreatePdf.createPdf(outputStream,tableValue,calendar,linePorts,piePort,barChartPort,stackedBarChartsPort);
        FileUtils.copyInputStreamToFile(new ByteArrayInputStream(outputStream.toByteArray()),new File("D:\\折线图/demo2.pdf"));
 
    }

使用CompletableFuture以后

    @Test
    void GeneratePdf() throws IOException, DocumentException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//        TableValue tableValue = Datas.formData();
        CompletableFuture<TableValue> tableValue = CompletableFuture.supplyAsync(() -> Datas.formData());
 
//        List<TableValue> calendar = Datas.calendar();
        CompletableFuture<List<TableValue>> calendar = CompletableFuture.supplyAsync(() -> Datas.calendar());
 
//        Map<String, Map<String, BigDecimal>> linePorts = Datas.createLinePorts();
        CompletableFuture<Map<String, Map<String, BigDecimal>>> linePorts = CompletableFuture.supplyAsync(() -> Datas.createLinePorts());
 
//        Map<String, BigDecimal> piePort = Datas.createPiePort();
        CompletableFuture<Map<String, BigDecimal>> piePort = CompletableFuture.supplyAsync(() -> Datas.createPiePort());
 
//        Map<String, Map<String, BigDecimal>> barChartPort = Datas.createBarChartPort();
        CompletableFuture<Map<String, Map<String, BigDecimal>>> barChartPort = CompletableFuture.supplyAsync(() -> Datas.createBarChartPort());
 
//        Map<String, Map<String, BigDecimal>> stackedBarChartsPort = Datas.createStackedBarChartsPort();
        CompletableFuture<Map<String, Map<String, BigDecimal>>> stackedBarChartsPort = CompletableFuture.supplyAsync(() -> Datas.createStackedBarChartsPort());
 
        CompletableFuture.allOf(tableValue,calendar,linePorts,piePort,barChartPort,stackedBarChartsPort);
 
//        CreateEchartsPdfUtils.createTable(outputStream,tableContentList,tableContentList2,createLinePorts(), tableContentList3,createPiePort(),tableContentList4,createLinePort(),createBarChartsPort(),createBarChartPort(),createStackedBarChartsPort());
        CreatePdf.createPdf(outputStream,tableValue.join(),calendar.join(),linePorts.join(),piePort.join(),barChartPort.join(),stackedBarChartsPort.join());
        FileUtils.copyInputStreamToFile(new ByteArrayInputStream(outputStream.toByteArray()),new File("D:\\折线图/demo2.pdf"));
 
    }

tips4

以上都是直接将文件在本地生成,实际使用中需要前端发送请求,我们将文件流给前端返回,该怎么做呢

 
    public void generateEDBPdf(HttpServletResponse response) throws IOException {
 
        String name = "要定义的文件名称";
        response.setHeader("Content-Disposition", "attachment;filename=" + name + ".pdf");
        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
        response.setContentType("application/pdf;charset=UTF-8");
 
        ServletOutputStream output = response.getOutputStream();
        BufferedOutputStream outputStream = new BufferedOutputStream(output);
 
//        CreatePdf.createPdf(outputStream,tableValue.join(),calendar.join(),linePorts.join(),piePort.join(),barChartPort.join(),stackedBarChartsPort.join());
 
    }

将CreatePdf.createPdf这个方法里的outputStream改为这个controller对应的Stream就好了

项目git地址 :https://gitee.com/mhjo/minghe/tree/jfree/

大家想推代码请新建分支~

原文链接:https://blog.csdn.net/lol19950605/article/details/128929870

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以使用 JFreeChart 和 iTextPDF生成包含中文方块的图表。下面是一个基本的示例代码: ```java import java.awt.Font; import java.io.FileOutputStream; import com.itextpdf.text.Document; import com.itextpdf.text.PageSize; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.BaseFont; import com.itextpdf.text.pdf.PdfContentByte; import com.itextpdf.text.pdf.PdfWriter; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.plot.PlotOrientation; import org.jfree.data.category.DefaultCategoryDataset; public class ChartExample { public static void main(String[] args) { // 创建数据集 DefaultCategoryDataset dataset = new DefaultCategoryDataset(); dataset.addValue(120, "Series 1", "Category 1"); dataset.addValue(240, "Series 1", "Category 2"); dataset.addValue(180, "Series 1", "Category 3"); dataset.addValue(90, "Series 1", "Category 4"); // 创建图表 JFreeChart chart = ChartFactory.createBarChart( "中文方块示例", "分类", "数值", dataset, PlotOrientation.VERTICAL, true, true, false ); // 设置字体 BaseFont bfChinese = null; try { bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); } catch (Exception e) { e.printStackTrace(); } Font fontChinese = new Font(bfChinese, 12, Font.NORMAL); chart.getTitle().setFont(fontChinese); chart.getCategoryPlot().getDomainAxis().setLabelFont(fontChinese); chart.getCategoryPlot().getRangeAxis().setLabelFont(fontChinese); chart.getLegend().setItemFont(fontChinese); // 生成PDF文件 try { Document document = new Document(PageSize.A4, 50, 50, 50, 50); PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("chart.pdf")); document.open(); PdfContentByte contentByte = writer.getDirectContent(); contentByte.setFontAndSize(bfChinese, 12); Paragraph paragraph = new Paragraph("中文方块示例", fontChinese); document.add(paragraph); com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(chart.createBufferedImage(500, 300)); document.add(image); document.close(); } catch (Exception e) { e.printStackTrace(); } } } ``` 这段代码将创建一个包含中文方块的柱状图,并将其保存为一个名为 "chart.pdf" 的PDF文件。你需要确保已经安装了中文字体,并将字体文件路径正确地指定在 `BaseFont.createFont` 方法中。 希望这个示例能帮到你!如有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值