jfreechart后台生成图片采样完美解决方案以及样式美化

众所周知,用jfreechart生成图片有很多问题,例如:x轴数据量多一点时,直接显示成了省略号

,y轴描述无法放到y轴正上方等等,另外jfreechart默认的样式也是比较丑的,如下:

 jfreechart在画x轴标签时,会根据图片设置的宽度、边距,以及x轴坐标数计算出一个平均值,即是每个坐标占用的最大宽度,超过这个宽度,会继续判断是否设置了参数:

//标签最大换行数
int maximumCategoryLabelLines

默认为0,不设置这个参数的话就不会进行换行,就会直接显示省略号。因此解决思路之一就是设置这个换行参数,或者适当增加图片宽度,可以在一定范围内解决x轴省略号的问题。但是当x轴数量太多,比如一张折线图有几百个数据点,也还是会超出可显示的范围。

对于横坐标出现重叠,也没有显示省略号,也没有换行显示的问题,一般是这个最大显示宽度计算出错的问题。

另外一种解决思路我是在网上找到的,就是对横坐标进行采样,不过经过了我自己的改良,那就是对jfreechart画x轴标签的代码进行修改覆盖,我们写一个类继承jfreechart包里面的

org.jfree.chart.axis.CategoryAxis

并重写refreshTicks方法,以便自己实现添加x轴标签的逻辑,重写的代码类如下:


import org.jfree.chart.axis.*;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.ui.RectangleEdge;
import org.jfree.text.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

public class IntervalCategoryAxis extends CategoryAxis {
    //抽样得到的数据索引
    private List<Integer> indexes;
    //x轴标签允许的换行数
    private int line ;

    public IntervalCategoryAxis(List<Integer> indexes,int line) {
        this.indexes = indexes;
        this.line = line;
    }
    private float getFloat(CategoryLabelPosition position,Rectangle2D dataArea, RectangleEdge edge){
        float l;
        if (position.getWidthType() == CategoryLabelWidthType.CATEGORY) {
            /*因为我们只显示了采样到的数据,因此这里计算最大宽度时根据样品数计算
            否则将导致标签换行不生效*/
            l = (float) calculateCategorySize(indexes.size(), dataArea,edge);
        }else {
            if (RectangleEdge.isLeftOrRight(edge)) {
                l = (float) dataArea.getWidth();
            } else {
                l = (float) dataArea.getHeight();
            }
        }
        return l;
    }
    /**
     * 重写获取横坐标的方法,只显示进行均匀采样得到的坐标
     */
    @Override
    public List<Tick> refreshTicks(Graphics2D g2, AxisState state, Rectangle2D dataArea, RectangleEdge edge) {
        List<Tick> ticks = new ArrayList<>();
        if (dataArea.getHeight() <= 0.0 || dataArea.getWidth() < 0.0) {
            return ticks;
        }
        CategoryPlot plot = (CategoryPlot) getPlot();
        List<?> categories = plot.getCategoriesForAxis(this);
        double max = 0.0;
        if (categories != null) {
            CategoryLabelPosition position = super.getCategoryLabelPositions().getLabelPosition(edge);
            float r = super.getMaximumCategoryLabelWidthRatio();
            if (r <= 0.0) {
                r = position.getWidthRatio();
            }
            float l = getFloat(position,dataArea,edge);
            //遍历所有数据
            for (int i = 0; i < categories.size(); i++) {
                Object o = categories.get(i);
                Comparable<?> category = (Comparable<?>) o;
                g2.setFont(getTickLabelFont(category));
                TextBlock label;
                if(line > 0){
                    label = createLabel(category, l * r, edge, g2);
                }else{
                    label = new TextBlock();
                    label.addLine(category.toString(), getTickLabelFont(category), getTickLabelPaint(category));
                }
                if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
                    max = Math.max(max, calculateTextBlockHeight(label, position, g2));
                } else if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) {
                    max = Math.max(max, calculateTextBlockWidth(label, position, g2));
                }
                //只添加采样的标签,对数据渲染不会有影响
                if(indexes.contains(i)){
                    Tick tick = new CategoryTick(category, label, position.getLabelAnchor(), position.getRotationAnchor(), position.getAngle());
                    ticks.add(tick);
                }
            }
        }
        state.setMax(max);
        return ticks;
    }

}

然后对横坐标标签进行采样展示,我们从大量的数据中只抽取固定数量的数据作为标签,然后添加到图片上。我这里使用的采样是均匀采样,具体想如何采样可以自己去实现,附上我自己写的采样工具类:

package com.zhou.chart.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 实现均匀采样(保留首尾)
 * @author lang.zhou
 * @date 2022/6/23 14:34
 */
public class HeadTailSample<T> extends Sample<T>{


    public HeadTailSample(List<T> sampleData, int sampleTotal) {
        this.sampleData = sampleData;
        this.sampleTotal = sampleTotal;
        this.sampleIndex = new ArrayList<>(sampleTotal);
    }

    public void execute(){
        int size = sampleData.size();
        float sec = (size+0.0f)/(sampleTotal - 1);
        if(size <= sampleTotal){

            return;
        }
        sampleIndex.add(0);
        float mark = sec;
        for (int i = 1; i < size; i++) {
            float cu = i + 0.0f;
            float rate = Math.abs(mark - cu);
            //补偿因子
            if(rate < 1){
                sampleIndex.add(i);
                //保证采样数量不多于sampleTotal
                if(sampleIndex.size()>= sampleTotal){
                    break;
                }
                mark+= sec;
            }
        }
        if(!sampleIndex.contains(size - 1)){
            if(sampleIndex.size()>= sampleTotal){
                sampleIndex.remove(sampleIndex.size() - 1);
            }
            sampleIndex.add(size - 1);
        }
    }

    public static void main(String[] args) {
        //5
        test(3,1,2,3,4,5,6,7,8);
        test(5,1,2,3,4,5);
        test(5,1,2,3,4,5,6);
        test(5,1,2,3,4,5,6,7);
        test(5,1,2,3,4,5,6,7,8);
        test(5,1,2,3,4,5,6,7,8,9);
        test(5,1,2,3,4,5,6,7,8,9,10);
        test(5,1,2,3,4,5,6,7,8,9,10,11);
        test(5,1,2,3,4,5,6,7,8,9,10,11,12);
        test(5,1,2,3,4,5,6,7,8,9,10,11,12,13);
        test(5,1,2,3,4,5,6,7,8,9,10,11,12,13,14);
        test(5,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
    }
    private static void test(int cnt,Integer ... a){
        List<Integer> l = Arrays.asList(a);
        Sample<Integer> sample = new HeadTailSample<>(l, cnt);
        sample.sample();
        List<Integer> list = sample.getResult();
        System.out.println(Arrays.toString(list.toArray()));
    }
}

采样的关键代码如下,list为数据,使用采样工具进行采样:

//对List<Map>进行采样,最多展示10个标签
HeadTailSample<Map<String,Object>> sample = new HeadTailSample<>(list,10);
sample.execute();
//使用我们重写的类来创建x轴标签
//最多允许换行数为3
CategoryAxis valueAxis = new IntervalCategoryAxis(sample.getSampleIndex(),3);
plot.setDomainAxis(valueAxis);

这样就能完美解决x轴显示不全的问题了,附上我测试生成的效果图:

 

 注意:设置了x轴标签倾斜后,x轴标签换行就不会生效了,但是我们仍然可以设置最多显示固定数量的标签。 

我这里对图片样式进行了美化,去掉了多余的边框,0轴显示实现,还修改了图形颜色和字体。

对于y轴描述无法放到y轴正上方,只能居中的问题,我的解决思路如下:

1. 隐藏原始的y轴标签

2. 自定义画一个标签

我们可以将自带的y轴标签设置为不显示,然后自己创建一个title,放到y轴的正上方,这样便能解决问题:

JFreeChart chart = ChartFactory.createLineChart(
                "",
                //不设置x轴和y轴描述,就默认不显示描述
                "",
                "",
                data,
                PlotOrientation.VERTICAL,
                //是否展示图例
                isShowLegend,
                false,
                false);
//图片标题
TextTitle textTitle = new TextTitle(title,titleFont);
textTitle.setPaint(Color.decode(color));
textTitle.setMargin(0,0,0,0);
chart.setTitle(textTitle);
List<Title> titles = new ArrayList<>(3);
//自己画一个y轴描述
TextTitle yn = null;
if(StringUtils.isNotBlank(yName)){
    yn = new TextTitle(yName,font);
    yn.setPaint(Color.decode(color));
    //放到左边
    yn.setHorizontalAlignment(HorizontalAlignment.LEFT);
    titles.add(yn);
}
//设置x轴描述
TextTitle xn = null;
if(StringUtils.isNotBlank(xName)){
    xn = new TextTitle(xName,font);
    //x轴描述放到右边
    xn.setHorizontalAlignment(HorizontalAlignment.RIGHT);
    //x轴描述放到底部
    xn.setPosition(RectangleEdge.BOTTOM);
    xn.setPaint(Color.decode(color));
    xn.setMargin(0,10,0,10);
    titles.add(xn);
}
if(titles.size() > 0){
    //设置额外标题
    chart.setSubtitles(titles);
}

注意:多个标题的显示顺序和添加到list中的顺序有关系,如果生成的图片中标签文字先后顺序有问题,可以对添加的顺序进行调整。

完整代码:

import com.alibaba.fastjson.JSON;
import com.zhou.util.TimeUtils;
import com.zhou.wordReport.chart.util.EvenSample;
import com.zhou.wordReport.chart.util.HeadTailSample;
import com.zhou.wordReport.chart.util.Sample;
import com.zhou.wordReport.common.Column;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jfree.chart.ChartColor;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.chart.title.Title;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.ui.*;

import java.awt.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import java.util.*;

/**
 * @author lang.zhou
 * @date 2022/6/24 17:16
 */
public class ChartUtil {
    //默认浅色调
    public static final String[] defaultLightColor = new String[]{
            "#acacac","#4f81bd","#c98193","#9dffff","#a2a2ff",
            "#01AF84","#003366","#C65353","#66CCCC","#4247A2",
            "#724356","#EB8270","#FFCCCC","#669999","#3366FF",
            "#CCCCFF","#FF6600","#FF9900","#666699","#FFE680"
    };

    public static List<Map<String,Object>> barTest(){
        List<Map<String,Object>> l = new ArrayList<>();
        l.add(JSON.parseObject("{a:'海通灵活配置组合-富国',v1:48,v2:28}"));
        l.add(JSON.parseObject("{a:'广汽集团组合-平安',v1:-1,v2:33}"));
        l.add(JSON.parseObject("{a:'汇众汽车组合-国寿',v1:62,v2:-20}"));
        l.add(JSON.parseObject("{a:'阿斯利康组合-平安',v1:27,v2:42}"));
        return l;
    }
    public static List<Map<String,Object>> pieTest(){
        List<Map<String,Object>> l = new ArrayList<>();
        l.add(JSON.parseObject("{name:'海通灵活配置组合-富国',value:48}"));
        l.add(JSON.parseObject("{name:'广汽集团组合-平安',value:16}"));
        l.add(JSON.parseObject("{name:'汇众汽车组合-国寿',value:32}"));
        l.add(JSON.parseObject("{name:'阿斯利康组合-平安',value:19}"));
        l.add(JSON.parseObject("{name:'沈飞工业组合-人保',value:8}"));
        return l;
    }
    public static void main(String[] args) throws Exception{
        LineChartOption o = JSON.parseObject("{maxViewScaleCount:1,showArrow:true,gridLineColor:'#'}",LineChartOption.class);
        List<Column> y = new ArrayList<>();
        y.add(new Column("v1","江西高速组合-泰康").setColor("#4f81bd"));
        y.add(new Column("v2","固定收益组合-国寿").setColor("#c98193"));
        Column x = new Column("a", "时间");
        LineChartOption lineChartOption = new LineChartOption();
        lineChartOption.setXColumn(x);
        lineChartOption.setYColumns(y);
        lineChartOption.setMaxViewScaleCount(10);
        lineChartOption.setXItalics(false);
        lineChartOption.setShowGridLine(true);
        lineChartOption.setShowXScaleLine(false);
        JFreeChart lineChart = ChartUtil.lineChart(lineTest(),lineChartOption);

        BarChartOption barChartOption = new BarChartOption();
        barChartOption.setTitle("2022年统计");
        barChartOption.setYName("金额(元)");
        barChartOption.setXColumn(x);
        barChartOption.setYColumns(y);
        barChartOption.setXLine(2);
        barChartOption.setMaxViewScaleCount(10);
        barChartOption.setShowLegend(true);
        barChartOption.setShowGridLine(true);
        barChartOption.setShowXScaleLine(false);
        JFreeChart barChart = ChartUtil.barChart(barTest(),barChartOption);

        PieChartOption pieChartOption = new PieChartOption();
        pieChartOption.setTitle("中海石油天野化工有限责任公司企业年金计划");
        pieChartOption.setDecimalBit(0);
        pieChartOption.setLegendPosition("bottom");
        pieChartOption.setLegendDirection("center");
        pieChartOption.setShowLegendBorder(false);
        pieChartOption.setName(new Column("name", "时间"));
        pieChartOption.setValue(new Column("value", "时间"));
        JFreeChart pieChart = ChartUtil.pieChart(pieTest(),pieChartOption);

        saveImage(lineChart, "d:line.jpg",lineChartOption);
        saveImage(barChart, "C:\\Users\\zhou\\Desktop\\bar.jpg",barChartOption);
        saveImage(pieChart, "C:\\Users\\zhou\\Desktop\\pie.jpg",pieChartOption);
    }
    public static JFreeChart chart(List<Map<String,Object>> result, AbstractChartOption opt){
        if(opt instanceof LineChartOption){
            return lineChart(result, (LineChartOption) opt);
        }else if(opt instanceof BarChartOption){
            return barChart(result, (BarChartOption) opt);
        }else if(opt instanceof PieChartOption){
            return pieChart(result, (PieChartOption) opt);
        }
        return null;
    }
    public static RectangleInsets getMargin(String margin){
        String[] split = margin.split("\\|");
        double[] r = new double[]{0d,0d,0d,0d};
        for (int i = 0; i < split.length; i++) {
            String s = split[i];
            r[i] = Double.parseDouble(s);
        }
        return new RectangleInsets(r[0],r[1],r[2],r[3]);
    }
    public static JFreeChart lineChart(List<Map<String,Object>> result, LineChartOption opt){
        Font normalFont = new Font(opt.getFontName(),Font.PLAIN,opt.getFontSize());
        Font titleFont = new Font(opt.getFontName(),opt.isTitleBold()?Font.BOLD:Font.PLAIN,opt.getTitleFontSize());
        DefaultCategoryDataset data = getBarData(result,opt.getXColumn(),opt.getYColumns());
        JFreeChart chart = ChartFactory.createLineChart(
                "",
                "",
                "",
                data,
                PlotOrientation.VERTICAL,
                false,
                false,
                false);
        //抗锯齿
        chart.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);

        RectangleInsets padding = new RectangleInsets(3,0,3,0);

        chart.setPadding(padding);
        //标题设置
        if(StringUtils.isNotBlank(opt.getTitle())){
            TextTitle textTitle = new TextTitle(opt.getTitle(),titleFont);
            textTitle.setPaint(Color.decode(opt.getFontColor()));
            chart.setTitle(textTitle);
        }
        //是否显示图片外边框
        chart.setBorderVisible(opt.isShowChartOuterBorder());
        //chart.setBorderPaint(Color.black);

        //画板设置
        CategoryPlot plot = (CategoryPlot)chart.getPlot();
        plot.setBackgroundPaint(ChartColor.WHITE);
        // 设置横虚线可见
        if(opt.isShowGridLine()){
            plot.setRangeGridlinesVisible(true);
            plot.setRangeGridlinePaint(Color.decode("#808080"));
        }
        //是否显示图片内边框
        plot.setOutlineVisible(opt.isShowChartInnerBorder());

        List<Title> titles = new ArrayList<>(3);
        //图例
        LegendTitle lg = null;
        if(opt.isShowLegend()){
            lg = createLegend(opt,chart,normalFont);
            if("top".equals(opt.getLegendPosition())){
                titles.add(lg);
            }
        }
        //y轴描述
        if(StringUtils.isNotBlank(opt.getYName())){
            TextTitle yn = new TextTitle(opt.getYName(),normalFont);
            yn.setPaint(Color.decode(opt.getFontColor()));
            yn.setHorizontalAlignment(HorizontalAlignment.LEFT);
            yn.setMargin(0,5,0,0);
            titles.add(yn);
        }
        if(lg != null && "bottom".equals(opt.getLegendPosition())){
            titles.add(lg);
        }
        //设置x轴描述
        if(StringUtils.isNotBlank(opt.getXName())){
            TextTitle xn = new TextTitle(opt.getXName(),normalFont);
            xn.setHorizontalAlignment(HorizontalAlignment.RIGHT);
            xn.setPosition(RectangleEdge.BOTTOM);
            xn.setPaint(Color.decode(opt.getFontColor()));
            xn.setMargin(0,0,0,5);
            titles.add(xn);
        }

        if(titles.size() > 0){
            chart.setSubtitles(titles);
        }
        //进行采样
        Sample<Map<String,Object>> sample ;
        if(Objects.equals(opt.getSampleType(),1)){
            sample = new HeadTailSample<>(result,opt.getMaxViewScaleCount());
        }else{
            sample = new EvenSample<>(result,opt.getMaxViewScaleCount());
        }
        sample.sample();
        //x轴设置
        //x轴标签采样
        CategoryAxis valueAxis = new IntervalCategoryAxis(sample.getSampleIndex(),opt.getXLine());

        //CategoryAxis valueAxis = plot.getDomainAxis();
        valueAxis.setUpperMargin(0);
        valueAxis.setLowerMargin(0);
        if(opt.isXItalics()){
            valueAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45);
        }

        valueAxis.setLabelFont(normalFont);
        valueAxis.setTickLabelFont(normalFont);
        valueAxis.setTickLabelPaint(Color.decode(opt.getFontColor()));
        valueAxis.setTickLabelsVisible(true);
        //是否展示x轴刻度基准线
        valueAxis.setAxisLineVisible(opt.isShowXScaleMainLine());
        //是否展示x轴刻度线
        valueAxis.setTickMarksVisible(opt.isShowXScaleLine());

        //允许横坐标标签换行数
        if(opt.getXLine() > 0){
            valueAxis.setMaximumCategoryLabelLines(opt.getXLine());
        }

        //0轴加粗
        if(opt.isZeroLineSolid()){
            ValueMarker marker = new ValueMarker(0, Color.GRAY, new BasicStroke(1.0F));
            plot.addRangeMarker(marker, Layer.FOREGROUND);
        }
        plot.setDomainAxis(valueAxis);
        //y轴设置
        NumberAxis numberAxis = (NumberAxis) plot.getRangeAxis();
        //设置纵坐标值的间距
        if(opt.getYSpace()>0) {
            numberAxis.setTickUnit(new NumberTickUnit(opt.getYSpace()));
        }
        numberAxis.setAxisLineVisible(opt.isShowYScaleMainLine());
        numberAxis.setTickMarksVisible(opt.isShowYScaleLine());
        numberAxis.setLabelFont(normalFont);
        numberAxis.setTickLabelFont(normalFont);
        numberAxis.setTickLabelPaint(Color.decode(opt.getFontColor()));
        //显示为百分数格式
        if(opt.isYPercent()){
            numberAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
            numberAxis.setNumberFormatOverride(NumberFormat.getPercentInstance());
        }
        numberAxis.setLowerMargin(0);
        numberAxis.setUpperMargin(0);
        //展示y轴箭头
        numberAxis.setPositiveArrowVisible(opt.isShowArrow());

        LineAndShapeRenderer lineRenderer = new LineAndShapeRenderer();
        //点不显示成方块状
        lineRenderer.setBaseShapesVisible(opt.isShowLineBlock());
        List<Column> y = opt.getYColumns();
        BasicStroke stroke = new BasicStroke(opt.getLineWidth());

        String[] pieColors = formatColorArray(opt.getColorArray());
        for (int i = 0; i < y.size(); i++) {
            try{
                if(i < pieColors.length){
                    lineRenderer.setSeriesPaint(i,Color.decode(pieColors[i]));
                }else if(i<defaultLightColor.length){
                    lineRenderer.setSeriesPaint(i,Color.decode(defaultLightColor[i]));
                }
            }catch (Exception e){
                //
            }
            lineRenderer.setSeriesStroke(i, stroke);
        }
        plot.setRenderer(lineRenderer);
        return chart;
    }
    public static JFreeChart barChart(List<Map<String,Object>> result, BarChartOption opt) {
        Font normalFont = new Font(opt.getFontName(),Font.PLAIN,opt.getFontSize());
        Font titleFont = new Font(opt.getFontName(),opt.isTitleBold()?Font.BOLD:Font.PLAIN,opt.getTitleFontSize());
        List<Column> y = opt.getYColumns();
        CategoryDataset data = getBarData(result,opt.getXColumn(),y);
        JFreeChart chart = ChartFactory.createBarChart(
                "",//图表标题
                "",//目录轴的显示标签
                "",//数值轴的显示标签
                data,//数据集
                opt.getDirection() == 0 ?
                        PlotOrientation.VERTICAL : PlotOrientation.HORIZONTAL,//图表方向:水平、垂直
                false,//是否显示图例
                false,//是否生成工具
                false);//是否生成URL链表
        chart.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
        RectangleInsets padding = new RectangleInsets(3,0,3,0);
        chart.setPadding(padding);
        //标题设置
        if(StringUtils.isNotBlank(opt.getTitle())){
            TextTitle textTitle = new TextTitle(opt.getTitle(),titleFont);
            textTitle.setPaint(Color.decode(opt.getFontColor()));
            chart.setTitle(textTitle);
        }
        //是否显示图片外边框
        chart.setBorderVisible(opt.isShowChartOuterBorder());
        //chart.setBorderPaint(Color.black);
        //画板设置
        CategoryPlot plot = (CategoryPlot)chart.getPlot();
        plot.setBackgroundPaint(ChartColor.WHITE);
        // 设置横虚线可见
        if(opt.isShowGridLine()){
            plot.setRangeGridlinesVisible(true);
            plot.setRangeGridlinePaint(Color.decode("#808080"));
        }
        //是否显示图片内边框
        plot.setOutlineVisible(opt.isShowChartInnerBorder());

        List<Title> titles = new ArrayList<>(3);
        //图例
        LegendTitle lg = null;
        if(opt.isShowLegend()){
            lg = createLegend(opt,chart,normalFont);
            if("top".equals(opt.getLegendPosition())){
                titles.add(lg);
            }
        }

        //y轴描述
        if(StringUtils.isNotBlank(opt.getYName())){
            TextTitle yn = new TextTitle(opt.getYName(),normalFont);
            yn.setPaint(Color.decode(opt.getFontColor()));
            yn.setHorizontalAlignment(HorizontalAlignment.LEFT);
            yn.setMargin(0,5,0,0);
            titles.add(yn);
        }
        if(lg != null && "bottom".equals(opt.getLegendPosition())){
            titles.add(lg);
        }
        //设置x轴描述
        if(StringUtils.isNotBlank(opt.getXName())){
            TextTitle xn = new TextTitle(opt.getXName(),normalFont);
            xn.setHorizontalAlignment(HorizontalAlignment.RIGHT);
            xn.setPosition(RectangleEdge.BOTTOM);
            xn.setPaint(Color.decode(opt.getFontColor()));
            xn.setMargin(0,0,0,5);
            titles.add(xn);
        }

        if(titles.size() > 0){
            chart.setSubtitles(titles);
        }

        //进行采样
        Sample<Map<String,Object>> sample ;
        if(Objects.equals(opt.getSampleType(),1)){
            sample = new HeadTailSample<>(result,opt.getMaxViewScaleCount());
        }else{
            sample = new EvenSample<>(result,opt.getMaxViewScaleCount());
        }
        sample.sample();
        //x轴设置
        //x轴标签采样
        CategoryAxis valueAxis = new IntervalCategoryAxis(sample.getSampleIndex(),opt.getXLine());

        valueAxis.setUpperMargin(0);
        valueAxis.setLowerMargin(0);
        if(opt.isXItalics()){
            valueAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45);
        }

        valueAxis.setLabelFont(normalFont);
        valueAxis.setTickLabelFont(normalFont);
        valueAxis.setTickLabelPaint(Color.decode(opt.getFontColor()));
        valueAxis.setTickLabelsVisible(true);
        //是否展示y轴刻度基准线
        valueAxis.setAxisLineVisible(opt.isShowXScaleMainLine());
        //是否展示y轴刻度线
        valueAxis.setTickMarksVisible(opt.isShowXScaleLine());
        //允许横坐标标签换行数
        if(opt.getXLine() > 0){
            valueAxis.setMaximumCategoryLabelLines(opt.getXLine());
        }

        //0轴加粗
        if(opt.isZeroLineSolid()){
            ValueMarker marker = new ValueMarker(0, Color.GRAY, new BasicStroke(1.0F));
            plot.addRangeMarker(marker, Layer.FOREGROUND);
        }
        plot.setDomainAxis(valueAxis);

        //y轴设置
        NumberAxis numberAxis = (NumberAxis) plot.getRangeAxis();
        //设置纵坐标值的间距
        if(opt.getYSpace()>0) {
            numberAxis.setTickUnit(new NumberTickUnit(opt.getYSpace()));
        }
        numberAxis.setAxisLineVisible(opt.isShowYScaleMainLine());
        numberAxis.setTickMarksVisible(opt.isShowYScaleLine());
        numberAxis.setLabelFont(normalFont);
        numberAxis.setTickLabelFont(normalFont);
        numberAxis.setTickLabelPaint(Color.decode(opt.getFontColor()));
        //显示为百分数格式
        if(opt.isYPercent()){
            numberAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
            numberAxis.setNumberFormatOverride(NumberFormat.getPercentInstance());
        }
        //展示y轴箭头
        numberAxis.setPositiveArrowVisible(opt.isShowArrow());
        double height = opt.getHeight() + 0d;

        numberAxis.setLowerMargin(10/height);
        numberAxis.setUpperMargin(opt.getTopMargin()/100);

        BarRenderer lineRenderer = new BarRenderer();
        //纯色填充,不使用渐变
        lineRenderer.setBarPainter(new StandardBarPainter());
        //lineRenderer.setBarPainter(new GradientBarPainter());
        lineRenderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
        lineRenderer.setBaseItemLabelsVisible(true);
        if(opt.getDirection() == 1){
            //横向柱状图
            lineRenderer.setBasePositiveItemLabelPosition(new ItemLabelPosition(
                    ItemLabelAnchor.INSIDE9, TextAnchor.CENTER_LEFT));
            lineRenderer.setBaseNegativeItemLabelPosition(new ItemLabelPosition(
                    ItemLabelAnchor.OUTSIDE3, TextAnchor.CENTER_LEFT));
            //x轴文字宽度最大20%
            valueAxis.setMaximumCategoryLabelWidthRatio(0.2f);

        }else{
            //负向的柱子标签也展示在上方
            lineRenderer.setBasePositiveItemLabelPosition(new ItemLabelPosition(
                    ItemLabelAnchor.OUTSIDE12, TextAnchor.CENTER));
            lineRenderer.setBaseNegativeItemLabelPosition(new ItemLabelPosition(
                    ItemLabelAnchor.OUTSIDE12, TextAnchor.CENTER));
        }
        lineRenderer.setItemLabelAnchorOffset(5D);

        //不显示阴影
        lineRenderer.setShadowVisible(false);

        String[] pieColors = formatColorArray(opt.getColorArray());
        for (int i = 0; i < y.size(); i++) {
            try{
                if(i < pieColors.length){
                    lineRenderer.setSeriesPaint(i,Color.decode(pieColors[i]));
                }else if(i<defaultLightColor.length){
                    lineRenderer.setSeriesPaint(i,Color.decode(defaultLightColor[i]));
                }
            }catch (Exception e){
                //
            }
        }
        plot.setRenderer(lineRenderer);

        return chart;
    }
    public static JFreeChart pieChart(List<Map<String,Object>> result, PieChartOption opt){
        Font normalFont = new Font(opt.getFontName(),Font.PLAIN,opt.getFontSize());
        Font titleFont = new Font(opt.getFontName(),Font.BOLD,opt.getTitleFontSize());
        DefaultPieDataset defaultPieDataset = getPieData(result,opt.getName(),opt.getValue());

        JFreeChart chart = ChartFactory.createPieChart("",defaultPieDataset,false,false,false);
        RectangleInsets padding = new RectangleInsets(2,2,2,2);
        chart.setPadding(padding);
        //标题设置
        if(StringUtils.isNotBlank(opt.getTitle())){
            TextTitle textTitle = new TextTitle(opt.getTitle(),titleFont);
            textTitle.setPaint(Color.decode(opt.getFontColor()));
            textTitle.setMargin(2,0,2,0);
            chart.setTitle(textTitle);
        }
        //是否显示图片外边框
        chart.setBorderVisible(opt.isShowChartOuterBorder());
        //chart.setBorderPaint(Color.black);
        List<Title> titles = new ArrayList<>(1);
        if(opt.isShowLegend()){
            LegendTitle lg = createLegend(opt,chart,normalFont);
            titles.add(lg);
        }
        if(titles.size() > 0){
            chart.setSubtitles(titles);
        }

        PiePlot plot=(PiePlot) chart.getPlot();//设置Plot
        plot.setBackgroundPaint(ChartColor.WHITE);
        plot.setLabelFont(normalFont);
        plot.setOutlineVisible(opt.isShowChartInnerBorder());

        DecimalFormat format ;
        if(opt.getDecimalBit() > 0){
            StringBuilder ds = new StringBuilder();
            for (int i = 0; i < opt.getDecimalBit(); i++) {
                ds.append("0");
            }
            format = new DecimalFormat("0." + ds.toString() + "%");
        }else{
            format = new DecimalFormat("0%");
        }
        plot.setLabelGenerator(new StandardPieSectionLabelGenerator(opt.getFormat(), NumberFormat.getNumberInstance(),format));
        plot.setLabelBackgroundPaint(null);
        if(!opt.isShowShadow()){
            plot.setShadowPaint(null);
        }
        plot.setLabelPaint(Color.decode(opt.getFontColor()));
        plot.setLabelShadowPaint(null);
        plot.setLabelOutlinePaint(null);
        plot.setCircular(true);
        plot.setBaseSectionOutlinePaint(Color.WHITE);

        String[] pieColors = formatColorArray(opt.getColorArray());

        @SuppressWarnings("unchecked")
        List<Comparable<String>> keys = defaultPieDataset.getKeys();
        for (int i = 0; i < keys.size(); i++) {
            Comparable<String> key = keys.get(i);
            try{
                if(i < pieColors.length){
                    plot.setSectionPaint(key,Color.decode(pieColors[i]));
                }else if(i<defaultLightColor.length){
                    plot.setSectionPaint(key,Color.decode(defaultLightColor[i]));
                }
            }catch (Exception e){
                //
            }
        }

        return chart;
    }
    private static String[] formatColorArray(String colorArray){
        String[] pieColors ;
        if(StringUtil.isNotBlank(colorArray)){
            pieColors = colorArray.replace(" ","").split(",");
        }else{
            pieColors = new String[0];
        }
        return pieColors;
    }
    public static Map<String,Object> getDefaultSettings(){
        Map<String,Object> r = new HashMap<>(40);
        r.putAll(JSON.parseObject(JSON.toJSONString(new LineChartOption())));
        r.putAll(JSON.parseObject(JSON.toJSONString(new BarChartOption())));
        r.putAll(JSON.parseObject(JSON.toJSONString(new PieChartOption())));
        return r;
    }
    private static LegendTitle createLegend(AbstractChartOption opt,JFreeChart chart,Font normalFont){
        LegendTitle lt = new LegendTitle(chart.getPlot());
        Font lf ;
        if(opt.isLegendBold()){
            lf = new Font(opt.getFontName(),Font.BOLD,opt.getFontSize());
        }else{
            lf = normalFont;
        }
        lt.setItemPaint(Color.decode(opt.getFontColor()));
        lt.setItemFont(lf);
        if(!opt.isShowLegendBorder()){
            lt.setBorder(0,0,0,0);
        }else{
            lt.setBorder(1,1,1,1);
        }
        lt.setMargin(1,1,1,1);
        if("top".equals(opt.getLegendPosition())){
            lt.setPosition(RectangleEdge.TOP);
        }else{
            lt.setPosition(RectangleEdge.BOTTOM);
        }
        if("left".equals(opt.getLegendDirection())){
            lt.setHorizontalAlignment(HorizontalAlignment.LEFT);
        }else if("right".equals(opt.getLegendDirection())){
            lt.setHorizontalAlignment(HorizontalAlignment.RIGHT);
        }else{
            lt.setHorizontalAlignment(HorizontalAlignment.CENTER);
        }
        return lt;
    }
    private static DefaultPieDataset getPieData(List<Map<String,Object>> result, Column name, Column value){
        DefaultPieDataset dpd=new DefaultPieDataset();
        if(CommonUtil.hasEmpty(result,name,value)){
            return dpd;
        }
        for(Map<String,Object> map : result){
            String nameV = MapUtils.getString(map,name.getColumnName(),"");
            Object o = map.get(value.getColumnName());
            if(o != null) {
                Double d = Double.parseDouble(o.toString());
                dpd.setValue(nameV, d);
            }else {
                dpd.setValue(nameV,null);
            }
        }
        return dpd;
    }
    private static DefaultCategoryDataset getBarData(List<Map<String,Object>> data, Column x, List<Column> y){
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        if(CommonUtil.hasEmpty(data,x,y)){
            return dataset;
        }
        for(Map<String,Object> map : data){
            String xV=MapUtils.getString(map,x.getColumnName(),"");
            for(Column c : y){
                Object o=map.get(c.getColumnName());
                if(o != null) {
                    Double n = Double.parseDouble(o.toString());
                    dataset.addValue(n, c.getColumnDesc(), xV);
                }else {
                    dataset.addValue(null,c.getColumnDesc(),xV);
                }
            }

        }
        return dataset;
    }
    public static void writeImage(JFreeChart chart, OutputStream stream, AbstractChartOption opt) throws IOException {
        if(opt == null){
            opt = new LineChartOption();
        }
        if(chart != null){
            // 将报表保存为jpeg文件
            ChartUtilities.writeChartAsJPEG(stream, chart, opt.getWidth(), opt.getHeight());
        }
    }
    public static void saveImage(JFreeChart chart, String filePathName, AbstractChartOption opt) throws IOException {
        if(opt == null){
            opt = new LineChartOption();
        }
        if(chart != null){
            FileOutputStream fileOutputStream = null;
            try {
                fileOutputStream = new FileOutputStream( filePathName);
                // 将报表保存为jpeg文件
                ChartUtilities.writeChartAsJPEG(fileOutputStream, chart, opt.getWidth(), opt.getHeight());
            } finally {
                IOUtils.closeQuietly(fileOutputStream);
            }

        }
    }
    public static List<Map<String,Object>> lineTest(){
        List<Map<String,Object>> l = new ArrayList<>();
        Date now = TimeUtils.formatDate("2022-01-01", "yyyy-MM-dd");

        Random r = new Random(new Date().getTime());
        for (int i = 0; i < 180; i++) {
            Date date = getNextDay(now,i);
            int v1 = 0;
            int v2 = 0;
            if(i < 100) {
                v1 = r.nextInt(200) - 50;
            }
                v2 = r.nextInt(100) - 30;

            String str = "{a:'"+TimeUtils.formatDate(date,"yyyy-MM-dd")+"',v1:"+v1+",v2:"+v2+"}";
            l.add(JSON.parseObject(str));
        }
        return l;
    }
    private static Date getNextDay(Date date,int n){
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.DAY_OF_YEAR, n);
        Date date3 = cal.getTime();
        return date3;
    }
}

 


import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.List;

/**
 * 柱状图默认配置类
 * @author lang.zhou
 * @date 2022/6/24 16:31
 */
@EqualsAndHashCode(callSuper = false)
@Data
public class BarChartOption extends AbstractChartOption{

    //x轴名称,为空不展示
    private String xName ;
    //y轴名称,为空不展示
    private String yName ;

    //图表是否展示虚线网格
    private boolean showGridLine = true;
    //y轴显示的两个刻度之间的间隔值
    private float ySpace = 0.0f;
    //0轴实线展示
    private boolean zeroLineSolid = true;
    //x轴刻度最多展示的标签的数量
    private int maxViewScaleCount = 5;
    //0-均匀采样 1-保留首尾均匀采样
    private int sampleType = 0;
    //y轴展示为百分数
    private boolean yPercent = false;
    //y轴展示箭头
    private boolean yArrow = true;
    //是否展示y轴刻度基准线(竖线)
    private boolean showYScaleMainLine = true;
    //是否展示y轴刻度线(短横线)
    private boolean showYScaleLine = true;
    //是否展示x轴刻度基准线(横线)
    private boolean showXScaleMainLine = true;
    //是否展示y轴刻度线(短竖线)
    private boolean showXScaleLine = true;
    //x轴是否倾斜
    private boolean xItalics = false;
    //x轴最大换行数
    private int xLine = 3;
    //y轴展示箭头
    private boolean showArrow = true;
    //柱子是否显示阴影
    //private boolean barShadow = false;
    //柱状图方向 0-竖向 1-横向
    private int direction = 0;

    //顶部边距,解决柱状图数值显示在图片外
    private double topMargin = 14.9D;
    //x轴
    private Column xColumn;
    //y轴
    private List<Column> yColumns;


}

 

import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.List;

/**
 * 折线图默认配置类
 * @author lang.zhou
 * @date 2022/6/24 16:31
 */
@EqualsAndHashCode(callSuper = false)
@Data
public class LineChartOption extends AbstractChartOption{
    //x轴名称,为空不展示
    private String xName ;
    //y轴名称,为空不展示
    private String yName ;

    //图表是否展示虚线网格
    private boolean showGridLine = true;
    //y轴显示的两个刻度之间的间隔值
    private float ySpace = 0.0f;
    //0轴实线展示
    private boolean zeroLineSolid = true;
    //x轴刻度最多展示的标签的数量
    private int maxViewScaleCount = 5;
    //0-均匀采样 1-保留首尾均匀采样
    private int sampleType = 0;
    //y轴展示为百分数
    private boolean yPercent = false;
    //是否展示y轴刻度基准线(竖线)
    private boolean showYScaleMainLine = true;
    //是否展示y轴刻度线(短横线)
    private boolean showYScaleLine = true;
    //是否展示x轴刻度基准线(横线)
    private boolean showXScaleMainLine = true;
    //是否展示y轴刻度线(短竖线)
    private boolean showXScaleLine = true;
    //x轴是否倾斜
    private boolean xItalics = false;
    //x轴最大换行数
    private int xLine = 3;
    //y轴展示箭头
    private boolean showArrow = true;
    //数据点是否显示小方块
    private boolean showLineBlock = false;

    //折线粗细,正常为1.0
    private float lineWidth = 2.0f;
    //x轴
    private Column xColumn;
    //y轴
    private List<Column> yColumns;

}

 

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 饼状图默认配置类
 * @author lang.zhou
 * @date 2022/6/24 16:31
 */
@EqualsAndHashCode(callSuper = false)
@Data
public class PieChartOption extends AbstractChartOption{

    //批注格式化,实例:招商证券=150(6.50%)
    private String format = "{0}={1}({2})" ;

    //百分数保留小数位
    private int decimalBit = 2 ;
    private boolean showShadow = false;
    //name
    private Column name;
    //value
    private Column value;


}

 


import lombok.Data;

/**
 * 图表公共属性
 * @author lang.zhou
 * @date 2022/6/24 17:00
 */
@Data
public abstract class AbstractChartOption {
    //图片宽高
    private int width = 500;
    private int height = 300;
    //图片内边距
    //private String margin = "0|0|0|0";
    //图片在word中的定位 1-center、0-left、2-right
    private int imageDirection = 1;
    //图片行宽占比(默认占整行,如需一行展示两个图片,设置为0.5)
    private int rowRate = 100;
    //图片是否显示外边框
    private boolean showChartOuterBorder = false;
    //图片是否显示内边框
    private boolean showChartInnerBorder = false;
    //图表中的默认字体
    private String fontName = "微软雅黑";
    //字体颜色
    private String fontColor = "#404040";
    //普通字体大小(x轴y轴标签、图例)
    private int fontSize = 12;
    //标题字体大小
    private int titleFontSize = 14;
    //标题字体加粗
    private boolean titleBold = true;
    //图片标题,为空不展示
    private String title ;

    //是否展示图例
    private boolean showLegend = true;
    //图例显示边框
    private boolean showLegendBorder = false;
    //图例字体加粗
    private boolean legendBold = false;
    //图例位置 上下
    private String legendPosition = "bottom";
    //图例位置 左右中
    private String legendDirection = "center";
    private String colorArray ;
}
import lombok.Data;

/**
 * @author lang.zhou
 * @date 2019/9/6
 */
@Data
public class Column {
    public Column(String columnName,String columnDesc){
        this.columnName=columnName;
        this.columnDesc=columnDesc;
    }
    private String columnName;
    private String columnDesc;
    private String dataType;
    private String color;


    public Column setColor(String color) {
        this.color = color;
        return this;
    }
}

public class EvenSample<T> extends Sample<T>{


    public EvenSample(List<T> sampleData, int sampleTotal) {
        this.sampleData = sampleData;
        this.sampleTotal = sampleTotal;
        this.sampleIndex = new ArrayList<>(sampleTotal);
    }
    @Override
    public void execute(){
        if(sampleTotal <= 0){
            throw new IllegalArgumentException("采样参数不正确");
        }
        int size = sampleData.size();
        float sec = (size+0.0f)/sampleTotal;
        if(size <= sampleTotal){
            return;
        }
        float mark = 0f;
        for (int i = 0; i < size; i++) {
            if(i+0.0f>=mark){
                sampleIndex.add(i);
                //保证采样数量不多于sampleTotal
                if(sampleIndex.size()>= sampleTotal){
                    break;
                }
                mark+= sec;
            }
        }
    }

    public static void main(String[] args) {
        List<Integer> l = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Sample<Integer> sample = new EvenSample<>(l, 5);
        sample.sample();
        List<Integer> list = sample.getResult();
        System.out.println(Arrays.toString(list.toArray()));
    }
}

 


import lombok.Getter;

import java.util.LinkedList;
import java.util.List;

/**
 * @author lang.zhou
 * @date 2022/8/25 10:47
 */
public abstract class Sample<T> {
    //样本数据
    protected List<T> sampleData;
    //抽取的样本数
    protected int sampleTotal;
    @Getter
    //采样的元素索引
    protected List<Integer> sampleIndex;
    public abstract void execute();
    public void sample(){
        if(sampleTotal <= 0){
            throw new IllegalArgumentException("采样参数不正确");
        }
        if(sampleData.size() <= sampleTotal){
            for (int i = 0; i < sampleData.size(); i++) {
                sampleIndex.add(i);
            }
        }else{
            this.execute();
        }
    }

    public List<T> getResult() {
        List<T> result = new LinkedList<>();
        for (Integer i : sampleIndex) {
            result.add(sampleData.get(i));
        }
        return result;
    }
}

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值