JFreeChart之堆疊柱形圖(StackedBar)
最近的項目使用有個功能需要使用到堆疊柱形圖,看了項目以前的代碼實現沒有想要的結果。所以自己就先到官網下載了 Demo,Demo里有個打包好的Jar包,直接運行看到效果,但是坑爹的是貌似沒有對應的源碼,所以只能根據class名稱直接google了,所幸在github里找到對應的源碼。
點我下載 訪問密碼 f62b
這是運行Demo找到想要實現的效果的大致圖:

我最終想要實現的效果是這樣的:

如果想要實現這個效果,可以使用
- extendedstackedbarrenderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator("{3}",
- NumberFormat.getPercentInstance(), new DecimalFormat("#0.0%")));
-
但是柱體的返回的值是10.1/50.1=20.2%,40.1/50.1=79.8%

這不符合預期目標,所以就去看了一下源碼,在StandardCategoryItemLabelGenerator的父類AbstractCategoryItemLabelGenerator中發現有createItemArray這么一個方法:

可以發現柱體的標簽值應該是由這個方法進行返回的,因此自己就對StandardCategoryItemLabelGenerator進行了繼承,並重寫了這個方法。
在ExtendedStandardCategoryItemLabelGeneratory中增加了isPercent作為標簽值是顯示百分比還是僅僅格式化的判斷參數,並重寫了createItemArray這個方法。

此時得到的效果如圖:

然而,柱體的總值還是沒有格式化。再看了一下ExtendedStackedBarRenderer這個類的代碼,找到了totalFormat這個屬性,將其值賦為new DecimalFormat("#0.0%")。測試:

基本是想要的最終結果,下面是測試代碼
- <dependency>
- <groupId>jfree</groupId>
- <artifactId>jfreechart</artifactId>
- <version>1.0.13</version>
- </dependency>
ExtendedStackedBarRenderer
- package com.springapp.jfreechar;
-
- import java.awt.Color;
- import java.awt.Font;
- import java.awt.Graphics2D;
- import java.awt.geom.Rectangle2D;
- import java.text.DecimalFormat;
- import java.text.NumberFormat;
-
- import org.jfree.chart.axis.CategoryAxis;
- import org.jfree.chart.axis.ValueAxis;
- import org.jfree.chart.entity.CategoryItemEntity;
- import org.jfree.chart.entity.EntityCollection;
- import org.jfree.chart.labels.CategoryToolTipGenerator;
- import org.jfree.chart.plot.CategoryPlot;
- import org.jfree.chart.plot.PlotOrientation;
- import org.jfree.chart.renderer.category.CategoryItemRendererState;
- import org.jfree.chart.renderer.category.StackedBarRenderer;
- import org.jfree.data.category.CategoryDataset;
- import org.jfree.text.TextUtilities;
- import org.jfree.ui.TextAnchor;
-
- public class ExtendedStackedBarRenderer extends StackedBarRenderer {
-
- private static final long serialVersionUID = 1L;
- private boolean showPositiveTotal;
- private boolean showNegativeTotal;
- private Font totalLabelFont;
- private NumberFormat totalFormatter;
- public ExtendedStackedBarRenderer() {
- showPositiveTotal = true;
- showNegativeTotal = true;
- totalLabelFont = new Font("SansSerif", 1, 12);
- totalFormatter = new DecimalFormat("#0.0%");
- }
-
- /**
- * StackedBarRenderer 沒有這個構造方法的,傳入一個NumberFormat類,可以自定義實現每個柱體值顯示格式
- * @param totalFormatter
- */
- public ExtendedStackedBarRenderer(NumberFormat totalFormatter) {
- showPositiveTotal = true;
- showNegativeTotal = true;
- totalLabelFont = new Font("SansSerif", 1, 12);
- this.totalFormatter = totalFormatter;
- }
-
- public NumberFormat getTotalFormatter() {
- return totalFormatter;
- }
-
- public void setTotalFormatter(NumberFormat numberformat) {
- if (numberformat == null) {
- throw new IllegalArgumentException("Null format not permitted.");
- } else {
- totalFormatter = numberformat;
- return;
- }
- }
-
- public void drawItem(Graphics2D graphics2d, CategoryItemRendererState categoryitemrendererstate, Rectangle2D rectangle2d, CategoryPlot categoryplot, CategoryAxis categoryaxis,
- ValueAxis valueaxis, CategoryDataset categorydataset, int i, int j, int k) {
- Number number = categorydataset.getValue(i, j);
- if (number == null)
- return;
- double d = number.doubleValue();
- PlotOrientation plotorientation = categoryplot.getOrientation();
- double d1 = categoryaxis.getCategoryMiddle(j, getColumnCount(), rectangle2d, categoryplot.getDomainAxisEdge()) - categoryitemrendererstate.getBarWidth() / 2D;
- double d2 = 0.0D;
- double d3 = 0.0D;
- for (int l = 0; l < i; l++) {
- Number number1 = categorydataset.getValue(l, j);
- if (number1 == null)
- continue;
- double d5 = number1.doubleValue();
- if (d5 > 0.0D)
- d2 += d5;
- else
- d3 += d5;
- }
-
- org.jfree.ui.RectangleEdge rectangleedge = categoryplot.getRangeAxisEdge();
- double d4;
- double d6;
- if (d > 0.0D) {
- d4 = valueaxis.valueToJava2D(d2, rectangle2d, rectangleedge);
- d6 = valueaxis.valueToJava2D(d2 + d, rectangle2d, rectangleedge);
- } else {
- d4 = valueaxis.valueToJava2D(d3, rectangle2d, rectangleedge);
- d6 = valueaxis.valueToJava2D(d3 + d, rectangle2d, rectangleedge);
- }
- double d7 = Math.min(d4, d6);
- double d8 = Math.max(Math.abs(d6 - d4), getMinimumBarLength());
- Rectangle2D.Double double1 = null;
- if (plotorientation == PlotOrientation.HORIZONTAL)
- double1 = new Rectangle2D.Double(d7, d1, d8, categoryitemrendererstate.getBarWidth());
- else
- double1 = new Rectangle2D.Double(d1, d7, categoryitemrendererstate.getBarWidth(), d8);
- java.awt.Paint paint = getItemPaint(i, j);
- graphics2d.setPaint(paint);
- graphics2d.fill(double1);
- if (isDrawBarOutline() && categoryitemrendererstate.getBarWidth() > 3D) {
- graphics2d.setStroke(getItemStroke(i, j));
- graphics2d.setPaint(getItemOutlinePaint(i, j));
- graphics2d.draw(double1);
- }
- org.jfree.chart.labels.CategoryItemLabelGenerator categoryitemlabelgenerator = getItemLabelGenerator(i, j);
- if (categoryitemlabelgenerator != null && isItemLabelVisible(i, j))
- drawItemLabel(graphics2d, categorydataset, i, j, categoryplot, categoryitemlabelgenerator, double1, d < 0.0D);
- if (d > 0.0D) {
- if (showPositiveTotal && isLastPositiveItem(categorydataset, i, j)) {
- graphics2d.setPaint(Color.black);
- graphics2d.setFont(totalLabelFont);
- double d9 = calculateSumOfPositiveValuesForCategory(categorydataset, j);
- float f = (float) double1.getCenterX();
- float f2 = (float) double1.getMinY() - 4F;
- TextAnchor textanchor = TextAnchor.BOTTOM_CENTER;
- if (plotorientation == PlotOrientation.HORIZONTAL) {
- f = (float) double1.getMaxX() + 4F;
- f2 = (float) double1.getCenterY();
- textanchor = TextAnchor.CENTER_LEFT;
- }
- TextUtilities.drawRotatedString(totalFormatter.format(d9), graphics2d, f, f2, textanchor, 0.0D, TextAnchor.CENTER);
- }
- } else if (showNegativeTotal && isLastNegativeItem(categorydataset, i, j)) {
- graphics2d.setPaint(Color.black);
- graphics2d.setFont(totalLabelFont);
- double d10 = calculateSumOfNegativeValuesForCategory(categorydataset, j);
- float f1 = (float) double1.getCenterX();
- float f3 = (float) double1.getMaxY() + 4F;
- TextAnchor textanchor1 = TextAnchor.TOP_CENTER;
- if (plotorientation == PlotOrientation.HORIZONTAL) {
- f1 = (float) double1.getMinX() - 4F;
- f3 = (float) double1.getCenterY();
- textanchor1 = TextAnchor.CENTER_RIGHT;
- }
- TextUtilities.drawRotatedString(totalFormatter.format(d10), graphics2d, f1, f3, textanchor1, 0.0D, TextAnchor.CENTER);
- }
- if (categoryitemrendererstate.getInfo() != null) {
- EntityCollection entitycollection = categoryitemrendererstate.getEntityCollection();
- if (entitycollection != null) {
- String s = null;
- CategoryToolTipGenerator categorytooltipgenerator = getToolTipGenerator(i, j);
- if (categorytooltipgenerator != null)
- s = categorytooltipgenerator.generateToolTip(categorydataset, i, j);
- String s1 = null;
- if (getItemURLGenerator(i, j) != null)
- s1 = getItemURLGenerator(i, j).generateURL(categorydataset, i, j);
- CategoryItemEntity categoryitementity = new CategoryItemEntity(double1, s, s1, categorydataset, categorydataset.getRowKey(i), categorydataset.getColumnKey(j));
- entitycollection.add(categoryitementity);
- }
- }
- }
-
- private boolean isLastPositiveItem(CategoryDataset categorydataset, int i, int j) {
- boolean flag = true;
- Number number = categorydataset.getValue(i, j);
- if (number == null)
- return false;
- for (int k = i + 1; k < categorydataset.getRowCount(); k++) {
- Number number1 = categorydataset.getValue(k, j);
- if (number1 != null)
- flag = flag && number1.doubleValue() <= 0.0D;
- }
-
- return flag;
- }
-
- private boolean isLastNegativeItem(CategoryDataset categorydataset, int i, int j) {
- boolean flag = true;
- Number number = categorydataset.getValue(i, j);
- if (number == null)
- return false;
- for (int k = i + 1; k < categorydataset.getRowCount(); k++) {
- Number number1 = categorydataset.getValue(k, j);
- if (number1 != null)
- flag = flag && number1.doubleValue() >= 0.0D;
- }
-
- return flag;
- }
-
- private double calculateSumOfPositiveValuesForCategory(CategoryDataset categorydataset, int i) {
- double d = 0.0D;
- for (int j = 0; j < categorydataset.getRowCount(); j++) {
- Number number = categorydataset.getValue(j, i);
- if (number == null)
- continue;
- double d1 = number.doubleValue();
- if (d1 > 0.0D)
- d += d1;
- }
-
- return d;
- }
-
- private double calculateSumOfNegativeValuesForCategory(CategoryDataset categorydataset, int i) {
- double d = 0.0D;
- for (int j = 0; j < categorydataset.getRowCount(); j++) {
- Number number = categorydataset.getValue(j, i);
- if (number == null)
- continue;
- double d1 = number.doubleValue();
- if (d1 < 0.0D)
- d += d1;
- }
-
- return d;
- }
-
- }
-
ExtendedStandardCategoryItemLabelGeneratory
- package com.springapp.jfreechar;
-
- import org.jfree.chart.HashUtilities;
- import org.jfree.chart.labels.AbstractCategoryItemLabelGenerator;
- import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
- import org.jfree.data.DataUtilities;
- import org.jfree.data.category.CategoryDataset;
- import org.jfree.util.ObjectUtilities;
- import org.jfree.util.PublicCloneable;
-
- import java.io.Serializable;
- import java.text.DateFormat;
- import java.text.MessageFormat;
- import java.text.NumberFormat;
-
- public class ExtendedStandardCategoryItemLabelGeneratory extends StandardCategoryItemLabelGenerator implements PublicCloneable, Cloneable, Serializable {
-
- private static final long serialVersionUID = -7108591260223293197L;
- private String labelFormat;
- private String nullValueString;
- private NumberFormat numberFormat;
- private DateFormat dateFormat;
- private NumberFormat percentFormat;
- //新增加了參數,柱體series的值是否是總值的百分比,還是僅僅將原來的小數轉化為百分數
- private boolean isPercent = false;
-
- public ExtendedStandardCategoryItemLabelGeneratory(String labelFormat, NumberFormat formatter, NumberFormat percentFormatter,boolean isPercent){
- super(labelFormat, formatter, percentFormatter);
- if(labelFormat == null) {
- throw new IllegalArgumentException("Null \'labelFormat\' argument.");
- } else if(formatter == null) {
- throw new IllegalArgumentException("Null \'formatter\' argument.");
- } else if(percentFormatter == null) {
- throw new IllegalArgumentException("Null \'percentFormatter\' argument.");
- } else {
- this.labelFormat = labelFormat;
- this.numberFormat = formatter;
- this.percentFormat = percentFormatter;
- this.dateFormat = null;
- this.nullValueString = "-";
- if (isPercent)
- this.isPercent = isPercent;
- }
-
- }
-
- @Override
- protected String generateLabelString(CategoryDataset dataset, int row, int column) {
- if(dataset == null) {
- throw new IllegalArgumentException("Null \'dataset\' argument.");
- } else {
- String result = null;
- Object[] items = this.createItemArray(dataset, row, column);
- result = MessageFormat.format(this.labelFormat, items);
- return result;
- }
- }
- @Override
- protected Object[] createItemArray(CategoryDataset dataset, int row, int column) {
- Object[] result = new Object[]{dataset.getRowKey(row).toString(), dataset.getColumnKey(column).toString(), null, null};
- Number value = dataset.getValue(row, column);
- if(value != null) {
- if(this.numberFormat != null) {
- result[2] = this.numberFormat.format(value);
- } else if(this.dateFormat != null) {
- result[2] = this.dateFormat.format(value);
- }
- } else {
- result[2] = this.nullValueString;
- }
-
- if(value != null) {
- double total = DataUtilities.calculateColumnTotal(dataset, column);
- //
- double percent = 0D;// / total;
- if (this.isPercent)
- //StandardCategoryItemLabelGenerator 返回的值是百分比
- percent = value.doubleValue() / total;
- else
- //返回自己原來的值
- percent = value.doubleValue();
- //格式化
- result[3] = this.percentFormat.format(percent);
- }
-
- return result;
- }
-
-
- }
-
StackedBarChartDemo3
- package com.springapp.jfreechar;
-
- import java.awt.*;
- import java.text.DecimalFormat;
- import java.text.NumberFormat;
- import java.util.Iterator;
-
- import javax.swing.*;
-
- import com.sun.javafx.charts.Legend;
- import org.jfree.chart.*;
- import org.jfree.chart.axis.*;
- import org.jfree.chart.labels.*;
- import org.jfree.chart.plot.CategoryPlot;
- import org.jfree.chart.plot.PlotOrientation;
- import org.jfree.chart.title.LegendTitle;
- import org.jfree.data.category.CategoryDataset;
- import org.jfree.data.category.DefaultCategoryDataset;
- import org.jfree.ui.ApplicationFrame;
- import org.jfree.ui.RectangleEdge;
- import org.jfree.ui.RefineryUtilities;
- import org.jfree.ui.TextAnchor;
-
- // Referenced classes of package demo:
- // ExtendedStackedBarRenderer
-
- public class StackedBarChartDemo3 extends ApplicationFrame {
-
- private static final long serialVersionUID = 1L;
-
- public StackedBarChartDemo3(String s) {
- super(s);
- JPanel jpanel = createDemoPanel();
- jpanel.setPreferredSize(new Dimension(500, 270));
- setContentPane(jpanel);
- }
-
- private static CategoryDataset createDataset() {
- DefaultCategoryDataset defaultcategorydataset = new DefaultCategoryDataset();
- defaultcategorydataset.addValue(.101D, "Series 1", "Cross Contamination (4.3)");
- defaultcategorydataset.addValue(.4D, "Series 2", "Cross Contamination (4.3)");
- return defaultcategorydataset;
- }
-
- private static JFreeChart createChart(CategoryDataset categorydataset) {
-
- JFreeChart jfreechart = ChartFactory.createStackedBarChart("Stacked Bar Chart Demo 3", "Category", "Value", categorydataset, PlotOrientation.VERTICAL, true, true, false);
-
- CategoryPlot categoryplot = (CategoryPlot) jfreechart.getPlot();
- //柱體
- ExtendedStackedBarRenderer extendedstackedbarrenderer = new ExtendedStackedBarRenderer(new DecimalFormat("#0.0%"));
- //柱體標簽是否可見
- extendedstackedbarrenderer.setBaseItemLabelsVisible(true);
- Font labelFont = new Font("Arial", Font.PLAIN, 12);
- extendedstackedbarrenderer.setBaseItemLabelPaint(new GradientPaint(0.0f, 0.0f, new Color(255, 255, 255), 0.0f, 0.0f, new Color(0, 0, 0)));
- //設置柱體標簽值的格式
- ExtendedStandardCategoryItemLabelGeneratory generator = new ExtendedStandardCategoryItemLabelGeneratory("{3}",
- NumberFormat.getPercentInstance(), new DecimalFormat("#0.0%"),false);
- extendedstackedbarrenderer.setBaseItemLabelGenerator(generator);
- //自定義柱體顏色
- Paint p0 = new GradientPaint(0.0f, 0.0f, new Color(237, 125, 49), 0.0f, 0.0f, new Color(237, 125, 49));
- extendedstackedbarrenderer.setSeriesPaint(0, p0);
- Paint p1 = new GradientPaint(0.0f, 0.0f, new Color(91, 155, 213), 0.0f, 0.0f, new Color(91, 155, 213));
- extendedstackedbarrenderer.setSeriesPaint(1, p1);
-
- categoryplot.setRenderer(extendedstackedbarrenderer);
-
- //Y 軸
- NumberAxis numberaxis = (NumberAxis) categoryplot.getRangeAxis();
- //不設置是自動顯示步長
- numberaxis.setTickUnit(new NumberTickUnit(0.05));
- numberaxis.setLowerMargin(0.14999999999999999D);
- numberaxis.setUpperMargin(0.14999999999999999D);
- //設置是否是百分率
- numberaxis.setNumberFormatOverride(NumberFormat.getPercentInstance());
-
- //X 輛
- CategoryAxis categoryAxis = categoryplot.getDomainAxis();
- //x軸旋轉
- categoryAxis.setCategoryLabelPositions(CategoryLabelPositions.createUpRotationLabelPositions(Math.PI / 6.0));
-
- //標注
- LegendTitle legendtitle = (LegendTitle)jfreechart.getLegend();
- legendtitle.setBorder(0, 0, 0, 0);
- return jfreechart;
- }
-
- public static JPanel createDemoPanel() {
- JFreeChart jfreechart = createChart(createDataset());
- return new ChartPanel(jfreechart);
- }
-
- public static void main(String args[]) {
- StackedBarChartDemo3 stackedbarchartdemo3 = new StackedBarChartDemo3("Stacked Bar Chart Demo 3");
- stackedbarchartdemo3.pack();
- RefineryUtilities.centerFrameOnScreen(stackedbarchartdemo3);
- stackedbarchartdemo3.setVisible(true);
- }
- }
-
-
