You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将生成的图表保存为图片而非显示在JPanel以优化性能?

预渲染图表为图片优化滚动性能

你的问题核心是每次滚动都会触发paintComponent重新计算并绘制所有图表元素,在低速设备上频繁重绘导致卡顿。将图表预渲染成BufferedImage后,滚动时只需直接绘制图片,能大幅降低CPU占用,提升流畅度。

修改后的代码实现

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;

public class Gf extends JPanel {
    private static final long serialVersionUID = 1L;
    private int padding = 0;
    private int labelPadding = 0;
    private Color pointColor = new Color(0, 10, 100);
    private int pointWidth = 1;
    private int numberYDivisions = 2;
    private List<Double> scores;
    private BufferedImage graphImage; // 存储预渲染的图表图片

    public Gf(List<Double> scores) {
        this.scores = scores;
        renderGraph(); // 初始化时渲染图片
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (graphImage != null) {
            // 直接绘制预渲染好的图片,无需重复计算
            g.drawImage(graphImage, 0, 0, this);
        }
    }

    // 单独的渲染方法,将图表绘制到BufferedImage中
    private void renderGraph() {
        Dimension size = getPreferredSize();
        if (size.width <= 0 || size.height <= 0 || scores == null || scores.isEmpty()) {
            graphImage = null;
            return;
        }

        // 创建对应尺寸的BufferedImage
        graphImage = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = graphImage.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        double xScale = ((double) size.width - (2 * padding) - labelPadding) / (scores.size() - 1);
        double yScale = ((double) size.height - 2 * padding - labelPadding) / 60000;

        List<Point> graphPoints = new ArrayList<>();
        for (int i = 0; i < scores.size(); i++) {
            int x1 = (int) (i * xScale + padding + labelPadding);
            int y1 = (int) ((getMaxScore() - scores.get(i)) * yScale + padding);
            graphPoints.add(new Point(x1, y1));
        }

        // 绘制背景
        g2.setColor(Color.BLACK);
        g2.fillRect(padding + labelPadding, padding,
                size.width - (2 * padding) - labelPadding,
                size.height - 2 * padding - labelPadding);
        g2.setColor(Color.BLACK);

        // Y轴刻度和标签
        for (int i = 0; i < numberYDivisions + 1; i++) {
            int x0 = padding + labelPadding;
            int x1 = pointWidth + padding + labelPadding;
            int y0 = size.height
                    - ((i * (size.height - padding * 2 - labelPadding)) / numberYDivisions
                    + padding + labelPadding);
            int y1 = y0;
            String yLabel = ((int) ((getMinScore()
                    + (getMaxScore() - getMinScore())
                    * ((i * 1.0) / numberYDivisions))
                    * 100))
                    / 100.0
                    + "";
            FontMetrics metrics = g2.getFontMetrics();
            int labelWidth = metrics.stringWidth(yLabel);
            g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (metrics.getHeight() / 2) - 3);
            g2.drawLine(x0, y0, x1, y1);
        }

        // X轴刻度和标签
        for (int i = 0; i < scores.size(); i++) {
            if (scores.size() > 1) {
                int x0 = i * (size.width - padding * 2 - labelPadding) / (scores.size() - 1)
                        + padding + labelPadding;
                int x1 = x0;
                int y0 = size.height - padding - labelPadding;
                int y1 = y0 - pointWidth;
                if ((i % ((int) ((scores.size() / 20.0)) + 1)) == 0) {
                    String xLabel = i + "";
                    FontMetrics metrics = g2.getFontMetrics();
                    int labelWidth = metrics.stringWidth(xLabel);
                    g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3);
                }
                g2.drawLine(x0, y0, x1, y1);
            }
        }

        // 绘制坐标轴
        g2.drawLine(padding + labelPadding, size.height - padding - labelPadding,
                padding + labelPadding, padding);
        g2.drawLine(padding + labelPadding, size.height - padding - labelPadding,
                size.width - padding, size.height - padding - labelPadding);

        Stroke oldStroke = g2.getStroke();
        g2.setStroke(oldStroke);
        g2.setColor(pointColor);
        // 绘制数据点
        for (Point point : graphPoints) {
            int x = point.x - pointWidth / 2;
            int y = point.y - pointWidth / 2;
            g2.fillOval(x, y, pointWidth, pointWidth);
        }

        g2.dispose(); // 释放资源
    }

    @Override
    public Dimension getPreferredSize() {
        // 如果设置过尺寸则返回,否则默认
        Dimension pref = super.getPreferredSize();
        return pref.width > 0 ? pref : new Dimension(3000, 276);
    }

    private double getMinScore() {
        return -30000;
    }

    private double getMaxScore() {
        return 30000;
    }

    public void setScores(List<Double> scores) {
        this.scores = scores;
        renderGraph(); // 数据变化时重新渲染图片
        repaint();
    }

    public List<Double> getScores() {
        return scores;
    }

    public static void createAndShowGui() {
        List<Double> scores = new ArrayList<>();
        short[] x = mainclass.Omega;
        for (short value : x) {
            scores.add((double) value);
        }
        Gf mainPanel = new Gf(scores);
        mainPanel.setPreferredSize(new Dimension(mainclass.range, 276));
        mainclass.p.add(mainPanel);
        mainclass.f.revalidate();
        mainclass.f.repaint();
    }
}

关键优化点说明

  1. 预渲染逻辑分离:把原来paintComponent里的绘制代码移到renderGraph()方法,一次性绘制到BufferedImage中,仅在初始化或数据变化时执行。
  2. 重绘简化:paintComponent现在只需要调用drawImage,避免了每次滚动都重新计算坐标、绘制大量元素的开销。
  3. 资源管理:使用g2.dispose()释放Graphics2D资源,避免内存泄漏。

额外建议

  • 如果图表尺寸极大(比如宽度超过10000),可以考虑分块渲染,只渲染当前可见区域的图片块,但3000宽度的场景下,全量预渲染已经足够高效。
  • 若需要支持动态缩放,可在缩放时重新调用renderGraph()更新图片。

内容的提问来源于stack exchange,提问作者user16179064

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 16:05:24