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


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

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

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

    1. //标签最大换行数
    2. int maximumCategoryLabelLines

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

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

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

    org.jfree.chart.axis.CategoryAxis

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

    1. import org.jfree.chart.axis.*;
    2. import org.jfree.chart.plot.CategoryPlot;
    3. import org.jfree.ui.RectangleEdge;
    4. import org.jfree.text.*;
    5. import java.awt.*;
    6. import java.awt.geom.Rectangle2D;
    7. import java.util.ArrayList;
    8. import java.util.List;
    9. public class IntervalCategoryAxis extends CategoryAxis {
    10. //抽样得到的数据索引
    11. private List<Integer> indexes;
    12. //x轴标签允许的换行数
    13. private int line ;
    14. public IntervalCategoryAxis(List<Integer> indexes,int line) {
    15. this.indexes = indexes;
    16. this.line = line;
    17. }
    18. private float getFloat(CategoryLabelPosition position,Rectangle2D dataArea, RectangleEdge edge){
    19. float l;
    20. if (position.getWidthType() == CategoryLabelWidthType.CATEGORY) {
    21. /*因为我们只显示了采样到的数据,因此这里计算最大宽度时根据样品数计算
    22. 否则将导致标签换行不生效*/
    23. l = (float) calculateCategorySize(indexes.size(), dataArea,edge);
    24. }else {
    25. if (RectangleEdge.isLeftOrRight(edge)) {
    26. l = (float) dataArea.getWidth();
    27. } else {
    28. l = (float) dataArea.getHeight();
    29. }
    30. }
    31. return l;
    32. }
    33. /**
    34. * 重写获取横坐标的方法,只显示进行均匀采样得到的坐标
    35. */
    36. @Override
    37. public List<Tick> refreshTicks(Graphics2D g2, AxisState state, Rectangle2D dataArea, RectangleEdge edge) {
    38. List<Tick> ticks = new ArrayList<>();
    39. if (dataArea.getHeight() <= 0.0 || dataArea.getWidth() < 0.0) {
    40. return ticks;
    41. }
    42. CategoryPlot plot = (CategoryPlot) getPlot();
    43. List<?> categories = plot.getCategoriesForAxis(this);
    44. double max = 0.0;
    45. if (categories != null) {
    46. CategoryLabelPosition position = super.getCategoryLabelPositions().getLabelPosition(edge);
    47. float r = super.getMaximumCategoryLabelWidthRatio();
    48. if (r <= 0.0) {
    49. r = position.getWidthRatio();
    50. }
    51. float l = getFloat(position,dataArea,edge);
    52. //遍历所有数据
    53. for (int i = 0; i < categories.size(); i++) {
    54. Object o = categories.get(i);
    55. Comparable<?> category = (Comparable<?>) o;
    56. g2.setFont(getTickLabelFont(category));
    57. TextBlock label;
    58. if(line > 0){
    59. label = createLabel(category, l * r, edge, g2);
    60. }else{
    61. label = new TextBlock();
    62. label.addLine(category.toString(), getTickLabelFont(category), getTickLabelPaint(category));
    63. }
    64. if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
    65. max = Math.max(max, calculateTextBlockHeight(label, position, g2));
    66. } else if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) {
    67. max = Math.max(max, calculateTextBlockWidth(label, position, g2));
    68. }
    69. //只添加采样的标签,对数据渲染不会有影响
    70. if(indexes.contains(i)){
    71. Tick tick = new CategoryTick(category, label, position.getLabelAnchor(), position.getRotationAnchor(), position.getAngle());
    72. ticks.add(tick);
    73. }
    74. }
    75. }
    76. state.setMax(max);
    77. return ticks;
    78. }
    79. }

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

    1. import lombok.Data;
    2. import java.util.*;
    3. /**
    4. * 实现均匀采样
    5. * @author lang.zhou
    6. * @date 2022/6/23 14:34
    7. */
    8. @Data
    9. public class EvenSample<T> {
    10. //样本数据
    11. private List<T> sampleData;
    12. //抽取的样本数
    13. private int sampleTotal;
    14. //采样结果
    15. private List<T> result;
    16. //采样的元素索引
    17. private List<Integer> sampleIndex;
    18. public EvenSample(List<T> sampleData, int sampleTotal) {
    19. this.sampleData = sampleData;
    20. this.sampleTotal = sampleTotal;
    21. this.sampleIndex = new ArrayList<>(sampleTotal);
    22. if(sampleData.size() <= sampleTotal){
    23. this.result = sampleData;
    24. for (int i = 0; i < sampleData.size(); i++) {
    25. sampleIndex.add(i);
    26. }
    27. }else{
    28. this.result = new ArrayList<>(sampleTotal);
    29. }
    30. }
    31. public EvenSample<T> execute(){
    32. if(sampleTotal <= 0){
    33. throw new IllegalArgumentException("采样参数不正确");
    34. }
    35. int size = sampleData.size();
    36. float sec = (size+0.0f)/sampleTotal;
    37. if(size <= sampleTotal){
    38. return this;
    39. }
    40. float mark = 0f;
    41. for (int i = 0; i < size; i++) {
    42. if(i+0.0f>=mark){
    43. sampleIndex.add(i);
    44. result.add(sampleData.get(i));
    45. //保证采样数量不多于sampleTotal
    46. if(result.size()>= sampleTotal){
    47. break;
    48. }
    49. mark+= sec;
    50. }
    51. }
    52. return this;
    53. }
    54. public static void main(String[] args) {
    55. List<Integer> l = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    56. List<Integer> list = new EvenSample<>(l, 5).execute().getResult();
    57. System.out.println(Arrays.toString(list.toArray()));
    58. }
    59. }

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

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

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

     

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

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

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

    1. 隐藏原始的y轴标签

    2. 自定义画一个标签

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

    1. JFreeChart chart = ChartFactory.createLineChart(
    2. "",
    3. //不设置x轴和y轴描述,就默认不显示描述
    4. "",
    5. "",
    6. data,
    7. PlotOrientation.VERTICAL,
    8. //是否展示图例
    9. isShowLegend,
    10. false,
    11. false);
    12. //图片标题
    13. TextTitle textTitle = new TextTitle(title,titleFont);
    14. textTitle.setPaint(Color.decode(color));
    15. textTitle.setMargin(0,0,0,0);
    16. chart.setTitle(textTitle);
    17. List<Title> titles = new ArrayList<>(3);
    18. //自己画一个y轴描述
    19. TextTitle yn = null;
    20. if(StringUtils.isNotBlank(yName)){
    21. yn = new TextTitle(yName,font);
    22. yn.setPaint(Color.decode(color));
    23. //放到左边
    24. yn.setHorizontalAlignment(HorizontalAlignment.LEFT);
    25. titles.add(yn);
    26. }
    27. //设置x轴描述
    28. TextTitle xn = null;
    29. if(StringUtils.isNotBlank(xName)){
    30. xn = new TextTitle(xName,font);
    31. //x轴描述放到右边
    32. xn.setHorizontalAlignment(HorizontalAlignment.RIGHT);
    33. //x轴描述放到底部
    34. xn.setPosition(RectangleEdge.BOTTOM);
    35. xn.setPaint(Color.decode(color));
    36. xn.setMargin(0,10,0,10);
    37. titles.add(xn);
    38. }
    39. if(titles.size() > 0){
    40. //设置额外标题
    41. chart.setSubtitles(titles);
    42. }

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

  • 相关阅读:
    Ae 效果:CC Lens
    【阿旭机器学习实战】【6】普通线性线性回归原理及糖尿病进展预测实战
    线程池阻塞队列长度设置失误导致任务一直被阻塞未能执行
    VuePress + Github Pages 搭建博客网站
    传统算法与神经网络算法,神经网络是机器算法吗
    波奇学C++:AVL树
    QT基础教程(Hello QT)
    11.The Metric Tensor
    【计算机网络】P2P文件分发介绍
    python3 词频统计计数分析+可视化词云 jieba+wordcloud 数据分析
  • 原文地址:https://blog.csdn.net/qq_36635569/article/details/125542169