如何使用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风格生成弧形边缘:
- 创建自定义
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); } } }
- 在原代码中替换
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
相关产品推荐
相关产品推荐

