jfreechart 生成 合并图表

结果图

maven

        <dependency>
            <groupId>org.jfree</groupId>
            <artifactId>jfreechart</artifactId>
            <version>1.5.2</version>
        </dependency>

图表数据封装

import lombok.Data;

import java.awt.*;
import java.util.Map;

/**
 * 图表数据封装
 * @author liaozesong
 */
@Data
public class ChartData {
    /**
     * 分组
     */
    private String group;
    /**
     * 折线名称
     */
    private String title;
    /**
     * 最小值
     */
    private Double min;
    /**
     * 最大值
     */
    private Double max;
    /**
     * 折线颜色
     */
    private Color color;
    /**
     * 折线数据
     * key->x轴
     * value->y轴
     */
    private Map<Long, Double> data;

    /**
     * 除data外所有属性复制
     */
    public ChartData copyBase() {
        ChartData chartData = new ChartData();
        chartData.setGroup(group);
        chartData.setTitle(title);
        chartData.setColor(color);
        chartData.setMax(max);
        chartData.setMin(min);
        return chartData;
    }
}

扩展 ChartFactory

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.chart.title.Title;
import org.jfree.chart.ui.RectangleInsets;
import org.jfree.data.xy.CategoryTableXYDataset;

import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Comparator;
import java.util.List;

/**
 * 扩展 ChartFactory
 * @author liaozesong
 */
@Slf4j
public class GtChartFactory extends ChartFactory {
    public static JFreeChart createLineChart(String title, List<List<ChartData>> data, Long minValues, Long maxValues) {
        NumberAxis xAxis = new NumberAxis();
        setX(xAxis, minValues, maxValues);

        CombinedDomainXYPlot plots = new CombinedDomainXYPlot(xAxis);
        setCombinedPlot(plots);

        for (int groupIndex = 0; groupIndex < data.size(); groupIndex++) {
            CategoryTableXYDataset dataset = new CategoryTableXYDataset();
            XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();

            List<ChartData> datum = data.get(groupIndex);

            //扩展的y轴 支持 \n 换行 label
            LineNumberAxis yAxis = new LineNumberAxis(datum.get(0).getGroup());
            setY(yAxis, datum);

            for (int i = 0; i < datum.size(); i++) {
                ChartData chartData = datum.get(i);
                chartData.getData().forEach((key, value) -> dataset.add(key, value, chartData.getTitle()));
                setRenderer(renderer, i, chartData.getColor());
            }

            XYPlot xyPlot = new XYPlot(dataset, groupIndex == 0 ? xAxis : null, yAxis, renderer);
            setPlot(xyPlot);
            plots.add(xyPlot);
        }

        JFreeChart chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT,
                plots, true);
        setChart(title, chart);
        return chart;
    }

    /**
     * 设置折线线段
     */
    private static void setRenderer(XYLineAndShapeRenderer renderer, int index, Color color) {
        //要求单点也要显示
        renderer.setSeriesShape(index, new Ellipse2D.Double(-0.25, -0.25, 0.5, 0.5));
        //设置折线颜色
        if (null != color) {
            renderer.setSeriesPaint(index, color);
        }
    }

    /**
     * 设置画图数据区域
     */
    private static void setPlot(XYPlot xyPlot) {
        //要求不显示边框
        xyPlot.setOutlineVisible(false);
    }

    /**
     * 设置多图合并区域
     */
    private static void setCombinedPlot(CombinedDomainXYPlot plots) {
        //图表无间隔
        plots.setGap(0);
    }

    /**
     * 设置x轴
     */
    private static void setX(NumberAxis numberAxis, Long minValues, Long maxValues) {
        numberAxis.setAutoRange(false);
        numberAxis.setRange(minValues, maxValues);
        //格式化刻度
        numberAxis.setNumberFormatOverride(new ChartKmFormat());
    }

    /**
     * 设置y轴
     */
    private static void setY(NumberAxis numberAxis, List<ChartData> data) {
        //y轴 label 由默认纵向转为横向
        numberAxis.setLabelAngle(Math.PI / 2.0);
        numberAxis.setLabelFont(new Font("宋体", Font.PLAIN, 8));
        if (CollUtil.isEmpty(data)) {
            return;
        }
        //根据配置 设置范围;无配置时 自适应
        try {
            Double min = data.stream().min(Comparator.comparing(ChartData::getMin)).get().getMin();
            Double max = data.stream().max(Comparator.comparing(ChartData::getMax)).get().getMax();
            numberAxis.setAutoRange(false);
            numberAxis.setRange(min, max);
        } catch (Exception e) {
        }
    }

    /**
     * 设置图表样式
     */
    private static void setChart(String title, JFreeChart chart) {
        chart.setBackgroundPaint(Color.white);
        chart.getLegend().setItemFont(new Font("宋体", Font.PLAIN, 8));
        //防止A4打印时,底部无墨
        chart.getLegend().setPadding(new RectangleInsets(1, 1, 20, 1));
        chart.setTitle(new TextTitle(
                title,
                new Font("宋体", Font.PLAIN, 12),
                TextTitle.DEFAULT_TEXT_PAINT, Title.DEFAULT_POSITION,
                Title.DEFAULT_HORIZONTAL_ALIGNMENT,
                Title.DEFAULT_VERTICAL_ALIGNMENT,
                //防止A4打印时,顶部无墨
                new RectangleInsets(20, 1, 1, 1)
        ));
    }

    /**
     * 自定义x轴 刻度 转换
     * 23311000 -> K23+311
     */
    static class ChartKmFormat extends NumberFormat {
        @Override
        public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
            number = number / 1000;
            int v = (int) number / 1000;
            int v1 = (int) number % 1000;
            return toAppendTo.append(StrUtil.format("K{}+{}", v, v1));
        }

        @Override
        public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
            number = number / 1000;
            int v = (int) number / 1000;
            int v1 = (int) number % 1000;
            return toAppendTo.append(StrUtil.format("K{}+{}", v, v1));
        }

        @Override
        public Number parse(String source, ParsePosition parsePosition) {
            return source.length();
        }
    }
}

扩展NumberAxis label 换行

import cn.hutool.core.util.StrUtil;
import org.jfree.chart.axis.AxisLabelLocation;
import org.jfree.chart.axis.AxisState;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.text.TextUtils;
import org.jfree.chart.ui.RectangleEdge;
import org.jfree.chart.ui.RectangleInsets;
import org.jfree.chart.ui.TextAnchor;

import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;


/**
 * 扩展NumberAxis
 * y轴 label 换行 \n
 * @author liaozesong
 */
public class LineNumberAxis extends NumberAxis {
    public LineNumberAxis(String label) {
        super(label);
    }

    /**
     * 重写Axis drawLabel方法
     */
    @Override
    protected AxisState drawLabel(String label, Graphics2D g2,
                                  Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge,
                                  AxisState state) {
        if (StrUtil.isEmpty(label)) {
            return state;
        }
        Font font = getLabelFont();
        RectangleInsets insets = getLabelInsets();
        g2.setFont(font);
        g2.setPaint(getLabelPaint());
        FontMetrics fm = g2.getFontMetrics();
        Rectangle2D labelBounds = TextUtils.getTextBounds(label, g2, fm);

        AffineTransform t = AffineTransform.getRotateInstance(
                getLabelAngle() - Math.PI / 2.0, labelBounds.getCenterX(),
                labelBounds.getCenterY());
        Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
        labelBounds = rotatedLabelBounds.getBounds2D();
        double labelx = state.getCursor()
                - insets.getRight() - labelBounds.getWidth() / 2.0;
        double labely = labelLocationY(AxisLabelLocation.MIDDLE, dataArea);
        TextAnchor anchor = labelAnchorV(AxisLabelLocation.MIDDLE);
        drawAlignedString(label, g2, (float) labelx,
                (float) labely,
                anchor);
        state.cursorLeft(insets.getLeft() + labelBounds.getWidth()
                + insets.getRight());

        return state;

    }


    public static void drawAlignedString(String text, Graphics2D g2,
                                         float x, float y, TextAnchor anchor) {
        Rectangle2D textBounds = new Rectangle2D.Double();
        float[] adjust = deriveTextBoundsAnchorOffsets(g2, text, anchor,
                textBounds);
        textBounds.setRect(x + adjust[0], y + adjust[1] + adjust[2],
                textBounds.getWidth(), textBounds.getHeight());
        //根据 \n 换行显示
        String[] split = StrUtil.split(text, "\n");
        for (int i = 0; i < split.length; i++) {
            g2.drawString(split[i], x + adjust[0], y + adjust[1] + 10 * i);
        }
    }

    /**
     * 无修改
     */
    private static float[] deriveTextBoundsAnchorOffsets(Graphics2D g2,
                                                         String text, TextAnchor anchor, Rectangle2D textBounds) {

        float[] result = new float[3];
        FontRenderContext frc = g2.getFontRenderContext();
        Font f = g2.getFont();
        FontMetrics fm = g2.getFontMetrics(f);
        Rectangle2D bounds = TextUtils.getTextBounds(text, g2, fm);
        LineMetrics metrics = f.getLineMetrics(text, frc);
        float ascent = metrics.getAscent();
        result[2] = -ascent;
        float halfAscent = ascent / 2.0f;
        float descent = metrics.getDescent();
        float leading = metrics.getLeading();
        float xAdj = 0.0f;
        float yAdj = 0.0f;

        if (anchor.isHorizontalCenter()) {
            xAdj = (float) -bounds.getWidth() / 2.0f;
        } else if (anchor.isRight()) {
            xAdj = (float) -bounds.getWidth();
        }

        if (anchor.isTop()) {
            yAdj = -descent - leading + (float) bounds.getHeight();
        } else if (anchor.isHalfAscent()) {
            yAdj = halfAscent;
        } else if (anchor.isVerticalCenter()) {
            yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0);
        } else if (anchor.isBaseline()) {
            yAdj = 0.0f;
        } else if (anchor.isBottom()) {
            yAdj = -metrics.getDescent() - metrics.getLeading();
        }
        if (textBounds != null) {
            textBounds.setRect(bounds);
        }
        result[0] = xAdj;
        result[1] = yAdj;
        return result;

    }

}

调用

/**
 * @author liaozesong
 */
public class ChartUtil {
    public static byte[] getChart(String title, List<List<ChartData>> data, Long min, Long max) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            JFreeChart chart = GtChartFactory.createLineChart(title, data, min, max);
            //2的倍数保存指定大小图片,防止图片模糊
            ChartUtils.writeScaledChartAsPNG(outputStream, chart, 842, 595, 2, 2);
            return outputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            IoUtil.close(outputStream);
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值