众所周知,用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;
}
}