在JFreeChart中制作自定义环形图表

我目前正在使用itext-pdf生成PDF。 除此之外,我还使用JFreeChart在其上创建图表。 我创建了一个爆炸效果的圆环图,它看起来像这样。

在此处输入图像描述

但是我想创建一个看起来更像这样的圆环图。

在此处输入图像描述

我希望某些作品脱颖而出,但不能完全脱离甜甜圈图表。 我非常感谢如何实现这一目标。

这是我目前的代码:

import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; import java.io.FileOutputStream; import java.io.IOException; import java.text.DecimalFormat; import java.util.Locale; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.labels.StandardPieSectionLabelGenerator; import org.jfree.chart.plot.PiePlotState; import org.jfree.chart.plot.RingPlot; import org.jfree.data.general.DefaultPieDataset; import org.jfree.ui.RectangleInsets; import com.itextpdf.awt.DefaultFontMapper; import com.itextpdf.text.BaseColor; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Element; import com.itextpdf.text.Font; import com.itextpdf.text.PageSize; import com.itextpdf.text.Phrase; import com.itextpdf.text.pdf.BaseFont; import com.itextpdf.text.pdf.ColumnText; import com.itextpdf.text.pdf.PdfContentByte; import com.itextpdf.text.pdf.PdfTemplate; import com.itextpdf.text.pdf.PdfWriter; public class RingChartTest { public static void main(String[] args) throws Exception { new RingChartTest().createPDF(); } private void createPDF() throws Exception { String destination = "ringchart.pdf"; Document document = new Document(PageSize.A4.rotate()); try { PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(destination)); document.open(); // Create the pages PdfContentByte cb = writer.getDirectContent(); addChart(cb); } catch (Exception e) { System.out.println("Failure to generate the PDF"); e.printStackTrace(); } finally { if (document != null) { document.close(); } } } private void addChart(PdfContentByte cb) throws Exception, IOException { long pctPM = Math.round(20); long pctOA = Math.round(15); long pctWPI = Math.round(5); long pctTDF = Math.round(25); long pctNE = 100 - (pctPM + pctOA + pctWPI + pctTDF); long pctEngaged = pctPM + pctOA + pctWPI; long numEngaged = 3400; String strNumEngaged = formatNumber(numEngaged, "#,###,###,##0"); JFreeChart chart = createChart(pctPM, pctOA, pctWPI, pctTDF, pctNE); int width = 300; int height = 200; PdfTemplate template = cb.createTemplate(width, height); Graphics2D graphics2d = template.createGraphics(width, height, new DefaultFontMapper()); Rectangle2D rectangle2d = new Rectangle2D.Double(0, 0, width, height); chart.draw(graphics2d, rectangle2d); graphics2d.dispose(); cb.addTemplate(template, 30, 185); // Add text inside chart Font engagementFont = createFont("OpenSans-Bold.ttf", 8, 116, 112, 100); Font percentFont1 = createFont("OpenSans-Light.ttf", 22, 116, 112, 100); Font percentFont2 = createFont("OpenSans-Light.ttf", 10, 116, 112, 100); Font numberFont = createFont("OpenSans-Regular.ttf", 8, 116, 112, 100); addPhrase(cb, "ENGAGE", engagementFont, 135, 290, 230, 310, 10, Element.ALIGN_CENTER); addPhrase(cb, String.valueOf(pctEngaged), percentFont1, 115, 270, 190, 289, 10, Element.ALIGN_RIGHT); addPhrase(cb, "%", percentFont2, 191, 275, 201, 299, 10, Element.ALIGN_LEFT); addPhrase(cb, "(" + strNumEngaged + ")", numberFont, 130, 258, 230, 278, 10, Element.ALIGN_CENTER); // Create legend // 290,420,370,520,10,Element.ALIGN_CENTER); BaseFont engagedPctFont = createBaseFont("OpenSans-Bold.ttf"); BaseFont engagedDescFont = createBaseFont("OpenSans-SemiBold.ttf"); BaseFont nonEngagedDescFont = createBaseFont("OpenSans-Regular.ttf"); BaseColor pmBaseColor = new BaseColor(31, 160, 200); BaseColor oaBaseColor = new BaseColor(84, 193, 209); BaseColor wpiBaseColor = new BaseColor(248, 156, 36); BaseColor tdfBaseColor = new BaseColor(116, 112, 94); BaseColor nonEngagedBaseColor = new BaseColor(148, 144, 132); float x = 330; float y = 350; float radius = 3; // Create border around legend /* cb.setColorFill(new BaseColor(255, 255, 255)); cb.rectangle(320, 300, 150, 70); cb.re cb.fill(); */ BaseColor borderColor = new BaseColor(192, 189, 178); cb.setColorStroke(borderColor); cb.moveTo(320, 300); cb.lineTo(320, 365); cb.lineTo(500, 365); cb.lineTo(500, 300); cb.lineTo(320, 300); cb.closePathStroke(); // Prof Mgmt cb.setColorFill(pmBaseColor); cb.circle(x, y, radius); cb.fill(); addTextToCanvas(cb, pctPM+"%", engagedPctFont, 8, new BaseColor(116, 112, 100), x+20, y-2); addTextToCanvas(cb, "Pg", engagedDescFont, 8, new BaseColor(116, 112, 100), x+50, y-2); // Online Advice cb.setColorFill(oaBaseColor); cb.circle(x, y-20, radius); cb.fill(); addTextToCanvas(cb, pctOA+"%", engagedPctFont, 8, new BaseColor(116, 112, 100), x+20, y-22); addTextToCanvas(cb, "Oaa", engagedDescFont, 8, new BaseColor(116, 112, 100), x+50, y-22); // Clicked WPI/Online Guidance cb.setColorFill(wpiBaseColor); cb.circle(x, y-40, radius); cb.fill(); addTextToCanvas(cb, pctWPI+"%", engagedPctFont, 8, new BaseColor(116, 112, 100), x+20, y-42); addTextToCanvas(cb, "Ogg", engagedDescFont, 8, new BaseColor(116, 112, 100), x+50, y-42); if (pctTDF > 0) { // TDF Users cb.setColorFill(tdfBaseColor); cb.circle(x, y-60, radius); cb.fill(); addTextToCanvas(cb, pctTDF+"%", engagedPctFont, 8, new BaseColor(116, 112, 100), x+20, y-62); addTextToCanvas(cb, "Pti*", nonEngagedDescFont, 8, new BaseColor(116, 112, 100), x+50, y-62); // Non-engaged cb.setColorFill(nonEngagedBaseColor); cb.circle(x, y-80, radius); cb.fill(); addTextToCanvas(cb, pctNE+"%", engagedPctFont, 8, new BaseColor(116, 112, 100), x+20, y-82); addTextToCanvas(cb, "Nng", nonEngagedDescFont, 8, new BaseColor(116, 112, 100), x+50, y-82); } else { // Non-engaged cb.setColorFill(nonEngagedBaseColor); cb.circle(x, y-60, radius); cb.fill(); addTextToCanvas(cb, pctNE+"%", engagedPctFont, 8, new BaseColor(116, 112, 100), x+20, y-62); addTextToCanvas(cb, "ngd", nonEngagedDescFont, 8, new BaseColor(116, 112, 100), x+50, y-62); } } private String formatNumber(double value, String strFormat) { DecimalFormat df = new DecimalFormat( strFormat ); return df.format(value); } private void addPhrase(PdfContentByte cb, String strText, Font font, float llx, float lly, float urx, float ury, float leading, int alignment) throws DocumentException { Phrase phrase = new Phrase(strText, font); ColumnText ct = new ColumnText(cb); ct.setSimpleColumn(phrase, llx, lly, urx, ury, leading, alignment); ct.go(); } private void addTextToCanvas(PdfContentByte cb, String strText, BaseFont font, float fontSize, BaseColor color, float x, float y) { cb.beginText(); cb.setFontAndSize(font, fontSize); cb.setColorFill(color); cb.showTextAligned(Element.ALIGN_LEFT, strText, x, y, 0); cb.endText(); } private BaseFont createBaseFont(String fileName) throws DocumentException, IOException { return BaseFont.createFont(PdfGenerationController.LOCATION_FONTS + fileName ,BaseFont.IDENTITY_H, BaseFont.EMBEDDED); } private Font createFont(String fileName, float size, int red, int green, int blue) throws DocumentException, IOException { BaseFont baseFont = BaseFont.createFont(PdfGenerationController.LOCATION_FONTS + fileName ,BaseFont.IDENTITY_H, BaseFont.EMBEDDED); Font font = new Font(baseFont, size); font.setColor(red, green, blue); return font; } public JFreeChart createChart(long pctPM, long pctOA, long pctWPI, long pctTDF, long pctNE) { // Set up the data set for the donut/ring chart DefaultPieDataset rDataSet = new DefaultPieDataset(); rDataSet.setValue("PM", pctPM ); rDataSet.setValue("OA", pctOA); rDataSet.setValue("WPI", pctWPI); rDataSet.setValue("TDF", pctTDF); rDataSet.setValue("NE", pctNE); // Initialize values boolean bShowLegend = false; String strTitle = null; // Create ring plot CustomDonutPlot rPlot = new CustomDonutPlot(rDataSet); //RingPlot rPlot = new RingPlot(rDataSet); rPlot.setLabelGenerator(new StandardPieSectionLabelGenerator(Locale.ENGLISH)); rPlot.setInsets(new RectangleInsets(0.0, 5.0, 5.0, 5.0)); rPlot.setSectionDepth(0.30); JFreeChart chart = new JFreeChart(strTitle, JFreeChart.DEFAULT_TITLE_FONT, rPlot, bShowLegend); ChartFactory.getChartTheme().apply(chart); // Create the chart //JFreeChart rChart = ChartFactory.createRingChart(null, rDataSet , false, false, Locale.ENGLISH); //RingPlot rPlot = (RingPlot) rChart.getPlot(); rPlot.setBackgroundPaint(Color.WHITE); rPlot.setCenterText(null); rPlot.setLabelGenerator(null); rPlot.setOutlineVisible(false); rPlot.setShadowGenerator(null); rPlot.setSeparatorsVisible(false); rPlot.setShadowPaint(null); rPlot.setSectionOutlinesVisible(false); rPlot.setOuterSeparatorExtension(0); rPlot.setInnerSeparatorExtension(0); // Set colors of the chart rPlot.setSectionPaint("PM", new Color(31, 160, 200)); rPlot.setSectionPaint("OA", new Color(84, 193, 209)); rPlot.setSectionPaint("WPI", new Color(248, 156, 36)); rPlot.setSectionPaint("TDF", new Color(116, 112, 94)); rPlot.setSectionPaint("NE", new Color(148, 144, 132)); rPlot.setExplodePercent("PM", 0.05); rPlot.setExplodePercent("OA", 0.05); rPlot.setExplodePercent("WPI", 0.05); return chart; } public static class CustomDonutPlot extends RingPlot { private static final long serialVersionUID = 1L; public CustomDonutPlot(DefaultPieDataset dataSet) { super(dataSet); } @Override protected void drawItem(Graphics2D g2, int section, Rectangle2D dataArea, PiePlotState state, int currentPass) { if (currentPass == 1 && section >=1 && section <= 3) { } Rectangle2D area = state.getPieArea(); System.out.println("*** At section=" + section + ", pass="+currentPass); logDataArea(dataArea, "Data area"); logDataArea(area, "Pie area"); System.out.println(state.getInfo()); super.drawItem(g2, section, dataArea, state, currentPass); } private void logDataArea(Rectangle2D dataArea, String msg) { System.out.println(msg + " h="+dataArea.getHeight() + ", w=" + dataArea.getWidth() + ", x=" + dataArea.getX() + ",y="+dataArea.getY()); } } } 

此备用版本将图表与PDF隔离。

 import java.awt.Color; import java.awt.EventQueue; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; import java.util.Locale; import javax.swing.JFrame; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.labels.StandardPieSectionLabelGenerator; import org.jfree.chart.plot.PiePlotState; import org.jfree.chart.plot.RingPlot; import org.jfree.data.general.DefaultPieDataset; import org.jfree.ui.RectangleInsets; /** * @see http://stackoverflow.com/q/37213030/230513 */ public class Test { private void display() { JFrame f = new JFrame("Test"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); long pctPM = Math.round(20); long pctOA = Math.round(15); long pctWPI = Math.round(5); long pctTDF = Math.round(25); long pctNE = 100 - (pctPM + pctOA + pctWPI + pctTDF); f.add(new ChartPanel(createChart(pctPM, pctOA, pctWPI, pctTDF, pctNE))); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); } public JFreeChart createChart(long pctPM, long pctOA, long pctWPI, long pctTDF, long pctNE) { // Set up the data set for the donut/ring chart DefaultPieDataset rDataSet = new DefaultPieDataset(); rDataSet.setValue("PM", pctPM); rDataSet.setValue("OA", pctOA); rDataSet.setValue("WPI", pctWPI); rDataSet.setValue("TDF", pctTDF); rDataSet.setValue("NE", pctNE); // Initialize values boolean bShowLegend = false; String strTitle = null; // Create ring plot CustomDonutPlot rPlot = new CustomDonutPlot(rDataSet); //RingPlot rPlot = new RingPlot(rDataSet); rPlot.setLabelGenerator(new StandardPieSectionLabelGenerator(Locale.ENGLISH)); rPlot.setInsets(new RectangleInsets(0.0, 5.0, 5.0, 5.0)); rPlot.setSectionDepth(0.30); JFreeChart chart = new JFreeChart(strTitle, JFreeChart.DEFAULT_TITLE_FONT, rPlot, bShowLegend); ChartFactory.getChartTheme().apply(chart); // Create the chart //JFreeChart rChart = ChartFactory.createRingChart(null, rDataSet , false, false, Locale.ENGLISH); //RingPlot rPlot = (RingPlot) rChart.getPlot(); rPlot.setBackgroundPaint(Color.WHITE); rPlot.setCenterText(null); rPlot.setLabelGenerator(null); rPlot.setOutlineVisible(false); rPlot.setShadowGenerator(null); rPlot.setSeparatorsVisible(false); rPlot.setShadowPaint(null); rPlot.setSectionOutlinesVisible(false); rPlot.setOuterSeparatorExtension(0); rPlot.setInnerSeparatorExtension(0); // Set colors of the chart rPlot.setSectionPaint("PM", new Color(31, 160, 200)); rPlot.setSectionPaint("OA", new Color(84, 193, 209)); rPlot.setSectionPaint("WPI", new Color(248, 156, 36)); rPlot.setSectionPaint("TDF", new Color(116, 112, 94)); rPlot.setSectionPaint("NE", new Color(148, 144, 132)); rPlot.setExplodePercent("PM", 0.05); rPlot.setExplodePercent("OA", 0.05); rPlot.setExplodePercent("WPI", 0.05); return chart; } public static class CustomDonutPlot extends RingPlot { private static final long serialVersionUID = 1L; public CustomDonutPlot(DefaultPieDataset dataSet) { super(dataSet); } @Override protected void drawItem(Graphics2D g2, int section, Rectangle2D dataArea, PiePlotState state, int currentPass) { super.drawItem(g2, section, dataArea, state, currentPass); Rectangle2D area = state.getPieArea(); System.out.println("*** At section=" + section + ", pass=" + currentPass); logDataArea(dataArea, "Data area"); logDataArea(area, "Pie area"); } private void logDataArea(Rectangle2D dataArea, String msg) { System.out.println(msg + " h=" + dataArea.getHeight() + ", w=" + dataArea.getWidth() + ", x=" + dataArea.getX() + ",y=" + dataArea.getY()); } } public static void main(String[] args) { EventQueue.invokeLater(new Test()::display); } } 

似乎您需要在未爆炸弧形的位置绘制爆炸弧。 为此,您可以覆盖RingPlot::getArcBounds并使用弧的边界。 更新您的代码(内部类)以获取下面的图像:

图片

 public static class CustomDonutPlot extends RingPlot { private static final long serialVersionUID = 1L; public CustomDonutPlot(DefaultPieDataset dataSet) { super(dataSet); } @Override protected void drawItem(Graphics2D g2, int section, Rectangle2D dataArea, PiePlotState state, int currentPass) { if (currentPass == 1 && section >=1 && section <= 3) { } Rectangle2D area = state.getPieArea(); System.out.println("*** At section=" + section + ", pass="+currentPass); logDataArea(dataArea, "Data area"); logDataArea(area, "Pie area"); System.out.println(state.getInfo()); super.drawItem(g2, section, dataArea, state, currentPass); } @Override protected Rectangle2D getArcBounds(Rectangle2D unexploded, Rectangle2D exploded, double angle, double extent, double explodePercent) { if(explodePercent > 0.0){ this.setSectionDepth(0.33);//to match inner arc java.awt.geom.Arc2D.Double arc1 = new java.awt.geom.Arc2D.Double(unexploded, angle, extent / 2.0D, 0); Point2D point1 = arc1.getEndPoint(); //java.awt.geom.Arc2D.Double arc2 = new java.awt.geom.Arc2D.Double(exploded, angle, extent / 2.0D, 0); //original code Rectangle2D mix = new Rectangle2D.Double(exploded.getX(), exploded.getY(), unexploded.getWidth(), unexploded.getHeight()); java.awt.geom.Arc2D.Double arc2 = new java.awt.geom.Arc2D.Double(mix, angle, extent / 2.0D, 0); Point2D point2 = arc2.getEndPoint(); double deltaX = (point1.getX() - point2.getX()) * explodePercent; double deltaY = (point1.getY() - point2.getY()) * explodePercent; //return new java.awt.geom.Rectangle2D.Double(unexploded.getX() - deltaX, unexploded.getY() - deltaY, unexploded.getWidth(), unexploded.getHeight()); original code return new java.awt.geom.Rectangle2D.Double(unexploded.getX() - deltaX, unexploded.getY() - deltaY, exploded.getWidth(), exploded.getHeight()); } else { this.setSectionDepth(0.3);//default depth return super.getArcBounds(unexploded, exploded, angle, extent, explodePercent); } } private void logDataArea(Rectangle2D dataArea, String msg) { System.out.println(msg + " h="+dataArea.getHeight() + ", w=" + dataArea.getWidth() + ", x=" + dataArea.getX() + ",y="+dataArea.getY()); } }