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

如何使用JFreeChart自定义带切片间隙与弧形边缘的环形饼图

JFreeChart环形饼图:添加切片间隙与弧形边缘

一、实现切片间隙

要让环形饼图的切片之间出现微小间隙,需启用分隔符并调整样式:

  • 开启分隔符可见:rp.setSeparatorsVisible(true);
  • 将分隔符颜色设为图表背景色(此处为白色),模拟间隙效果:rp.setSeparatorPaint(Color.WHITE);
  • 通过setSeparatorStroke设置分隔符宽度,控制间隙大小:rp.setSeparatorStroke(new BasicStroke(2.0f));
  • 调整内外圈分隔符延伸长度,保证切片全边缘都有间隙:
    rp.setOuterSeparatorExtension(1.0);
    rp.setInnerSeparatorExtension(0.90);
    

二、实现弧形切片边缘

JFreeChart默认切片边缘为平直样式,需自定义RingPlot的绘制逻辑,使用Arc2D的ROUND风格生成弧形边缘:

  1. 创建自定义CustomRingPlot类继承RingPlot,重写drawItem方法:
class CustomRingPlot extends RingPlot {
    public CustomRingPlot(PieDataset dataset) {
        super(dataset);
    }

    @Override
    protected void drawItem(Graphics2D g2, int section, Rectangle2D dataArea, PieSectionEntity entity) {
        double angle0 = getStartAngle();
        double angle1 = angle0 + getSectionAngle(section);
        double innerRadius = getInnerRadius();
        double outerRadius = getOuterRadius();
        
        // 创建带弧形边缘的环形路径
        Arc2D outerArc = new Arc2D.Double(dataArea, Math.toDegrees(angle0), Math.toDegrees(angle1 - angle0), Arc2D.ROUND);
        Arc2D innerArc = new Arc2D.Double(dataArea, Math.toDegrees(angle1), Math.toDegrees(angle0 - angle1), Arc2D.ROUND);
        
        GeneralPath path = new GeneralPath();
        path.append(outerArc, false);
        path.lineTo(
            dataArea.getCenterX() + innerRadius * Math.cos(angle1),
            dataArea.getCenterY() + innerRadius * Math.sin(angle1)
        );
        path.append(innerArc, false);
        path.lineTo(
            dataArea.getCenterX() + outerRadius * Math.cos(angle0),
            dataArea.getCenterY() + outerRadius * Math.sin(angle0)
        );
        path.closePath();
        
        // 绘制切片填充与边框
        Paint paint = getSectionPaint(section);
        g2.setPaint(paint);
        g2.fill(path);
        Stroke stroke = getSectionOutlineStroke(section);
        Paint outlinePaint = getSectionOutlinePaint(section);
        if (stroke != null && outlinePaint != null) {
            g2.setStroke(stroke);
            g2.setPaint(outlinePaint);
            g2.draw(path);
        }
        
        // 处理交互实体(可选)
        if (entity != null) {
            entity.setArea(path.getBounds2D());
            getEntityCollection().add(entity);
        }
    }
}
  1. 在原代码中替换RingPlot的创建方式,使用自定义类:
// 替换原RingPlot获取逻辑
// RingPlot rp = (RingPlot)chart.getPlot();
CustomRingPlot rp = new CustomRingPlot(dataset);
chart.setPlot(rp);

修改后的完整代码

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.RingPlot;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
import org.jfree.ui.RectangleInsets;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.CenterTextMode;

import java.awt.*;
import java.awt.geom.Arc2D;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.imageio.ImageIO;
import javax.xml.bind.DatatypeConverter;

class CustomRingPlot extends RingPlot {
    public CustomRingPlot(PieDataset dataset) {
        super(dataset);
    }

    @Override
    protected void drawItem(Graphics2D g2, int section, Rectangle2D dataArea, PieSectionEntity entity) {
        double angle0 = getStartAngle();
        double angle1 = angle0 + getSectionAngle(section);
        double innerRadius = getInnerRadius();
        double outerRadius = getOuterRadius();
        
        Arc2D outerArc = new Arc2D.Double(dataArea, Math.toDegrees(angle0), Math.toDegrees(angle1 - angle0), Arc2D.ROUND);
        Arc2D innerArc = new Arc2D.Double(dataArea, Math.toDegrees(angle1), Math.toDegrees(angle0 - angle1), Arc2D.ROUND);
        
        GeneralPath path = new GeneralPath();
        path.append(outerArc, false);
        path.lineTo(
            dataArea.getCenterX() + innerRadius * Math.cos(angle1),
            dataArea.getCenterY() + innerRadius * Math.sin(angle1)
        );
        path.append(innerArc, false);
        path.lineTo(
            dataArea.getCenterX() + outerRadius * Math.cos(angle0),
            dataArea.getCenterY() + outerRadius * Math.sin(angle0)
        );
        path.closePath();
        
        Paint paint = getSectionPaint(section);
        g2.setPaint(paint);
        g2.fill(path);
        Stroke stroke = getSectionOutlineStroke(section);
        Paint outlinePaint = getSectionOutlinePaint(section);
        if (stroke != null && outlinePaint != null) {
            g2.setStroke(stroke);
            g2.setPaint(outlinePaint);
            g2.draw(path);
        }
        
        if (entity != null) {
            entity.setArea(path.getBounds2D());
            getEntityCollection().add(entity);
        }
    }
}

private String createCustomPieChart(Map<String, Float> pieData, String pieChart) {
    AtomicInteger count = new AtomicInteger(0);
    Integer size = pieData.size();
    try {
        DefaultPieDataset<String> dataset = new DefaultPieDataset<String>();
        pieData.entrySet().stream().forEach(e -> dataset.setValue(e.getKey(), e.getValue()));        
        JFreeChart chart = ChartFactory.createRingChart("Chart Title", dataset, true, false, false);
        chart.setBorderVisible(false);
        chart.getLegend().setBackgroundPaint(Color.WHITE);
        chart.setBackgroundPaint(Color.WHITE);
        
        // 使用自定义环形图
        CustomRingPlot rp = new CustomRingPlot(dataset);
        chart.setPlot(rp);
        
        rp.setBackgroundPaint(Color.WHITE);
        rp.setSectionDepth(0.10);
        rp.setLabelGap(0.5);
        rp.setOutlineVisible(false);
        
        // 动态生成切片颜色
        pieData.entrySet().stream().forEach(entry -> {
            rp.setSectionPaint(entry.getKey(), Color.getHSBColor((float)count.get()/size, 1, 1)); 
            count.getAndIncrement();
        });
        
        // 配置切片间隙
        rp.setSeparatorsVisible(true);
        rp.setSeparatorPaint(Color.WHITE);
        rp.setSeparatorStroke(new BasicStroke(2.0f));
        rp.setOuterSeparatorExtension(1.0);
        rp.setInnerSeparatorExtension(0.90);
        
        rp.setCircular(true);
        rp.setAutoPopulateSectionOutlineStroke(true);
        rp.setInteriorGap(0.25);
        
        rp.setLegendItemShape(new Rectangle(20,20));
        
        rp.setLabelBackgroundPaint(Color.WHITE);
        rp.setLabelOutlinePaint(Color.WHITE);
        rp.setLabelGenerator(new StandardPieSectionLabelGenerator("{0}:{1}"));
        rp.setSimpleLabelOffset(new RectangleInsets(RectangleInsets.UNIT_TYPE_RELATIVE, 0.09, 0.09, 0.09, 0.09));
        rp.setSimpleLabels(true);
        
        // 设置中心文本
        rp.setCenterTextMode(CenterTextMode.FIXED); 
        rp.setCenterText("Total Split 500");
        
        // 生成Base64图片
        BufferedImage bufferedImage = chart.createBufferedImage(500, 500);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(bufferedImage, "png", baos);
        return pieChart.replace("graph.jpg", "data:image/png;base64,"+DatatypeConverter.printBase64Binary(baos.toByteArray()));
    }catch (Exception e) {
        e.printStackTrace();
        log.error("Exception from Pie Image generate: {}", e.getMessage());
    }
    return "";
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 11:04:52