众所周知,用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;
- }
-
- }
然后对横坐标标签进行采样展示,我们从大量的数据中只抽取固定数量的数据作为标签,然后添加到图片上。我这里使用的采样是均匀采样,具体想如何采样可以自己去实现,附上我自己写的采样工具类:
-
- import lombok.Data;
-
- import java.util.*;
-
- /**
- * 实现均匀采样
- * @author lang.zhou
- * @date 2022/6/23 14:34
- */
- @Data
- public class EvenSample<T> {
- //样本数据
- private List<T> sampleData;
- //抽取的样本数
- private int sampleTotal;
-
- //采样结果
- private List<T> result;
-
- //采样的元素索引
- private List<Integer> sampleIndex;
-
- public EvenSample(List<T> sampleData, int sampleTotal) {
- this.sampleData = sampleData;
- this.sampleTotal = sampleTotal;
- this.sampleIndex = new ArrayList<>(sampleTotal);
- if(sampleData.size() <= sampleTotal){
- this.result = sampleData;
- for (int i = 0; i < sampleData.size(); i++) {
- sampleIndex.add(i);
- }
- }else{
- this.result = new ArrayList<>(sampleTotal);
- }
- }
-
- public EvenSample<T> execute(){
- if(sampleTotal <= 0){
- throw new IllegalArgumentException("采样参数不正确");
- }
- int size = sampleData.size();
- float sec = (size+0.0f)/sampleTotal;
- if(size <= sampleTotal){
- return this;
- }
- float mark = 0f;
- for (int i = 0; i < size; i++) {
- if(i+0.0f>=mark){
- sampleIndex.add(i);
- result.add(sampleData.get(i));
- //保证采样数量不多于sampleTotal
- if(result.size()>= sampleTotal){
- break;
- }
- mark+= sec;
- }
- }
- return this;
- }
-
- public static void main(String[] args) {
- List<Integer> l = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
- List<Integer> list = new EvenSample<>(l, 5).execute().getResult();
- System.out.println(Arrays.toString(list.toArray()));
- }
- }
采样的关键代码如下,list为数据,使用采样工具进行采样:
- //对List<Map>进行采样,最多展示10个标签
- EvenSample<Map<String,Object>> sample = new EvenSample<>(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);
- }
注意:多个标题的显示顺序和添加顺序有关系,如果生成的图片中标签文字先后顺序有问题,可以对添加的顺序进行调整。