2

Consider a smaller version of this example:

public class LayeredBarChartDemo2 extends ApplicationFrame {

    public LayeredBarChartDemo2(final String title) {
        super(title);
        final double[][] data = new double[][] { { 55, 60 }, { 25.0, 13.0 } };

        final CategoryDataset dataset = DatasetUtils.createCategoryDataset("Series ", "Factor ", data);

        // create the chart...
        final CategoryAxis categoryAxis = new CategoryAxis("Category");
        final ValueAxis valueAxis = new NumberAxis("Score (%)");

        final CategoryPlot plot = new CategoryPlot(dataset, categoryAxis, valueAxis, new LayeredBarRenderer());
        final JFreeChart chart = new JFreeChart("Layered Bar Chart Demo 2", JFreeChart.DEFAULT_TITLE_FONT, plot, true);

        final LayeredBarRenderer renderer = (LayeredBarRenderer) plot.getRenderer();

        // add the chart to a panel...
        final ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
        setContentPane(chartPanel);

    }

    public static void main(final String[] args) {
        final LayeredBarChartDemo2 demo = new LayeredBarChartDemo2("Layered Bar Chart Demo 2");
        demo.pack();
        demo.setVisible(true);
    }
}

It produces:

result

I want to add labels on it, and make it look like this:

want

I have tried what works for other renderers:

renderer.setDefaultItemLabelsVisible(true);

Or:

renderer.setSeriesItemLabelsVisible(0, true);
renderer.setSeriesItemLabelsVisible(1, true);

I also tried to declare a CategoryItemLabelGenerator explicitly:

renderer.setDefaultItemLabelGenerator(new CategoryItemLabelGenerator() {

    @Override
    public String generateRowLabel(CategoryDataset dataset, int row) {
        return "sasa";
    }

    @Override
    public String generateLabel(CategoryDataset dataset, int row, int column) {
        return "lalal";
    }

    @Override
    public String generateColumnLabel(CategoryDataset dataset, int column) {
        return "ababa";
    }
});

(I also tried using setSeriesItemLabelGenerator(series, CategoryLabelGenerator) too).

I changed the font & the paint in case they are there but I just do not see them.

I use JFreeChart 1.5.0.

Is there a way to add the labels?

George Z.
  • 6,643
  • 4
  • 27
  • 47

2 Answers2

2

The apparent bug identified here by @George Z. hinges on an incorrect value calculated by the LayeredBarRenderer implementation of drawVerticalItem() when it calls drawItemLabel() as shown here. The predicate transX1 > transX2 should be inverted.

As a workaround when using v1.5, it's possible to specify a negative ItemLabelPosition, as shown the variation below.

ItemLabelPosition position = new ItemLabelPosition(
    ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_CENTER);
renderer.setDefaultNegativeItemLabelPosition(position);

PlotOrientation.VERTICAL: PlotOrientation.VERTICAL

As the implementation of drawHorizontalItem() appears correct, a positive ItemLabelPosition works as expected.

ItemLabelPosition position = new ItemLabelPosition(
    ItemLabelAnchor.OUTSIDE3, TextAnchor.CENTER_LEFT);
renderer.setDefaultPositiveItemLabelPosition(position);

PlotOrientation.HORIZONTAL: PlotOrientation.HORIZONTAL

In addition, a StandardCategoryToolTipGenerator works as expected. You can customize the DEFAULT_TOOL_TIP_FORMAT_STRING as shown here and here.

renderer.setDefaultToolTipGenerator(new StandardCategoryToolTipGenerator());

Code:

import java.awt.Dimension;
import java.awt.EventQueue;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtils;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.labels.StandardCategoryToolTipGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.LayeredBarRenderer;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.DatasetUtils;
import org.jfree.chart.ui.ApplicationFrame;
import org.jfree.chart.ui.TextAnchor;

/** @see https://stackoverflow.com/a/63464855/230513 */
public class LayeredBarChartDemo2 extends ApplicationFrame {
    
    private static final String TITLE = "Layered Bar Chart Demo 2";
    
    public LayeredBarChartDemo2(final String title) {
        super(title);
        final double[][] data = new double[][]{{55, 60}, {25, 13}};
        final CategoryDataset dataset = DatasetUtils.createCategoryDataset("Series ", "Factor ", data);
        final CategoryAxis categoryAxis = new CategoryAxis("Category");
        final ValueAxis valueAxis = new NumberAxis("Score (%)");
        final LayeredBarRenderer renderer = new LayeredBarRenderer();
        renderer.setDefaultToolTipGenerator(new StandardCategoryToolTipGenerator());
        renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());
        renderer.setDefaultItemLabelsVisible(true);
        ItemLabelPosition position = new ItemLabelPosition(
            ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_CENTER);
        renderer.setDefaultNegativeItemLabelPosition(position);
        final CategoryPlot plot = new CategoryPlot(dataset, categoryAxis, valueAxis, renderer);
        plot.setOrientation(PlotOrientation.VERTICAL);
        final JFreeChart chart = new JFreeChart(TITLE, JFreeChart.DEFAULT_TITLE_FONT, plot, true);
        ChartUtils.applyCurrentTheme(chart);
        add(new ChartPanel(chart) {
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(640, 480);
            }
        });
    }
    
    public static void main(final String[] args) {
        EventQueue.invokeLater(() -> {
            final LayeredBarChartDemo2 demo = new LayeredBarChartDemo2(TITLE);
            demo.pack();
            demo.setLocationRelativeTo(null);
            demo.setVisible(true);
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • 1
    Unfortunately, I use tool tips already to show other things to the user. I found a solution my own last night (EU). I will mark it as an answer whenever I am able to (hopefully I will not forget it). If you want, give it a look. Either way, thank you for your time. – George Z. Aug 18 '20 at 08:39
  • 1
    Nice one. In this point I wanna mention that I tested all combinations of `OUTSIDEx/INSIDEx` and `TextAnchor.SOMETHING` manually. Not my best experience. I just hope you did not do it too. I mark your answer as the correct one. No point to mark mine. – George Z. Aug 20 '20 at 15:29
2

I found the solution myself and it is an unorthodox one, but at least it seems to do what I want.

In LayeredBarRenderer#drawVerticalItem there is this part:

// draw the item labels if there are any...
CategoryItemLabelGenerator generator = getItemLabelGenerator(row, column);
if (generator != null && isItemLabelVisible(row, column)) {
    double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
    double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
    drawItemLabel(g2, dataset, row, column, plot, generator, bar, (transX1 > transX2));
}

I extended LayeredBarRenderer and @Override this method with the transX1>transX2 inverted to transX1<=transX2:

//....
// draw the item labels if there are any...
CategoryItemLabelGenerator generator = getItemLabelGenerator(row, column);
if (generator != null && isItemLabelVisible(row, column)) {
    double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
    double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
    drawItemLabel(g2, dataset, row, column, plot, generator, bar, !(transX1 > transX2)); //here
}
//....

This combined with an exclusive CategoryItemLabelGenerator gave me the result I want:

renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());
renderer.setSeriesItemLabelsVisible(0, true);
renderer.setSeriesItemLabelsVisible(1, true);

The result:

result

The full overrided class:

@SuppressWarnings("serial")
public class LabelFixedLayeredBarRenderer extends LayeredBarRenderer {
    @Override
    protected void drawVerticalItem(Graphics2D g2, CategoryItemRendererState state, Rectangle2D dataArea, CategoryPlot plot,
            CategoryAxis domainAxis, ValueAxis rangeAxis, CategoryDataset dataset, int row, int column) {
        // nothing is drawn for null values...
        Number dataValue = dataset.getValue(row, column);
        if (dataValue == null) {
            return;
        }

        // BAR X
        double rectX = domainAxis.getCategoryMiddle(column, getColumnCount(), dataArea, plot.getDomainAxisEdge())
                - state.getBarWidth() / 2.0;

        int seriesCount = getRowCount();

        // BAR Y
        double value = dataValue.doubleValue();
        double base = 0.0;
        double lclip = getLowerClip();
        double uclip = getUpperClip();

        if (uclip <= 0.0) { // cases 1, 2, 3 and 4
            if (value >= uclip) {
                return; // bar is not visible
            }
            base = uclip;
            if (value <= lclip) {
                value = lclip;
            }
        } else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
            if (value >= uclip) {
                value = uclip;
            } else {
                if (value <= lclip) {
                    value = lclip;
                }
            }
        } else { // cases 9, 10, 11 and 12
            if (value <= lclip) {
                return; // bar is not visible
            }
            base = getLowerClip();
            if (value >= uclip) {
                value = uclip;
            }
        }

        RectangleEdge edge = plot.getRangeAxisEdge();
        double transY1 = rangeAxis.valueToJava2D(base, dataArea, edge);
        double transY2 = rangeAxis.valueToJava2D(value, dataArea, edge);
        double rectY = Math.min(transY2, transY1);

        double rectWidth;
        double rectHeight = Math.abs(transY2 - transY1);

        // draw the bar...
        double shift = 0.0;
        double widthFactor = 1.0;
        double seriesBarWidth = getSeriesBarWidth(row);
        if (!Double.isNaN(seriesBarWidth)) {
            widthFactor = seriesBarWidth;
        }
        rectWidth = widthFactor * state.getBarWidth();
        rectX = rectX + (1 - widthFactor) * state.getBarWidth() / 2.0;
        if (seriesCount > 1) {
            // needs to be improved !!!
            shift = rectWidth * 0.20 / (seriesCount - 1);
        }

        Rectangle2D bar = new Rectangle2D.Double((rectX + ((seriesCount - 1 - row) * shift)), rectY,
                (rectWidth - (seriesCount - 1 - row) * shift * 2), rectHeight);

        if (state.getElementHinting()) {
            beginElementGroup(g2, dataset.getRowKey(row), dataset.getColumnKey(column));
        }

        Paint itemPaint = getItemPaint(row, column);
        GradientPaintTransformer t = getGradientPaintTransformer();
        if (t != null && itemPaint instanceof GradientPaint) {
            itemPaint = t.transform((GradientPaint) itemPaint, bar);
        }
        g2.setPaint(itemPaint);
        g2.fill(bar);

        if (isDrawBarOutline() && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
            g2.setStroke(getItemOutlineStroke(row, column));
            g2.setPaint(getItemOutlinePaint(row, column));
            g2.draw(bar);
        }

        if (state.getElementHinting()) {
            endElementGroup(g2);
        }

        // draw the item labels if there are any...
        CategoryItemLabelGenerator generator = getItemLabelGenerator(row, column);
        if (generator != null && isItemLabelVisible(row, column)) {
            double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
            double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
            drawItemLabel(g2, dataset, row, column, plot, generator, bar, !(transX1 > transX2));
        }

        // collect entity and tool tip information...
        EntityCollection entities = state.getEntityCollection();
        if (entities != null) {
            addItemEntity(entities, dataset, row, column, bar);
        }
    }
}

P.S: I did not test if it works by default when the plot is oriented horizontally and if it requires to do the same thing for drawHorizontalItem.

George Z.
  • 6,643
  • 4
  • 27
  • 47
  • 1
    Invoking `plot.setOrientation(PlotOrientation.HORIZONTAL)` with the existing renderer reveals labels, but the positions need adjustment. – trashgod Aug 18 '20 at 11:44