3

Recently I get a requirement to create a bar chart that show data for each project. Here's an example :

Example of the bar chart

As you can see, the Category is the name of the project, and Series are different types of data in that project.

However, since system dose not guarantee the uniqueness of project name, using it as categories can cause problem, and I will not able to use project name to generate URL for different projects. On the other hand, If I use unique id as category, I won't able to display the project name. This put me into a difficult situation.

So my problem is :

Is there a way to generate customize category label on the fly in JFreeChart ?

Something similar to CategoryItemLabelGenerator but for category itself. So I can use unique Id as category, but display project name in chart.

Rangi Lin
  • 9,303
  • 6
  • 45
  • 71

2 Answers2

4

The answer depends on how your chosen CategoryDataset implements the KeyedValues2D interface. The interface expects the keys to be unique, and the default implementation, DefaultKeyedValues2D, requires that the keys be Comparable and immutable.

Unique String instances are the typical concrete parameter type, but nothing in JFreeChart enforces the unique constraint. One approach wold be to wrap your String in a class that implements Comparable and also enforces uniqueness. The class Value is an example that leverages the underlying Double implementation. Your implementation would require an additional attribute to distinguish one project form another, perhaps using the primary key of the source relation. You could override toString() to get a formatted representation of the name.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
2

This is how I implement UniqueValue

public class UniqueValue implements Comparable<UniqueValue> {

    private final String uniqueId;
    private final String value;

    public UniqueValue(String uniqueId, String value) {
        this.uniqueId = uniqueId;
        this.value = value;
    }

    @Override
    public int compareTo(UniqueValue o) {
        return uniqueId.compareTo(o.uniqueId);
    }

    @Override
    public int hashCode() {
        return uniqueId.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof UniqueValue) {
            return uniqueId.equals(((UniqueValue)obj).uniqueId);
        }
        return false;
    }

    @Override
    public String toString() {
            return value;
        }
    }

You can use this class to deal with the uniqueness issue I mentioned in questions.


UPDATE

However, since JFreeChart use toString() method to create the label for categories. So the toString() implementation in UniqueValue can be pretty weird. So here is another attempt.

First, a generator interface

public interface CategoryLabelGenerator {
    public String generate(Comparable<?> category);
}

then I make a subclass for CategoryAxis

public class CategoryLabelCustomizableCategoryAxis extends CategoryAxis {

    private static final long serialVersionUID = 1L;
    private CategoryLabelGenerator labelGenerator;

    public CategoryLabelCustomizableCategoryAxis(String label) {
        super(label);
    }

    public void setCategoryLabelGenerator(CategoryLabelGenerator generator) {
        this.labelGenerator = generator;
    }

    @Override
    protected TextBlock createLabel(Comparable category, float width, 
        RectangleEdge edge, Graphics2D g2) {
        if (generator == null) {
            return super.createLabel(category, width, edge, g2);
        }
        return TextUtilities.createTextBlock(
            labelGenerator.generate(category),  // generate label for category on the fly
            getTickLabelFont(category), getTickLabelPaint(category), width,
            getMaximumCategoryLabelLines(), new G2TextMeasurer(g2));
    }
}

example :

JFreeChart chart = makeChart();
CategoryPlot plot = chart.getCategoryPlot();

CategoryAxis axis = new CategoryLabelCustomizableCategoryAxis();
axis.setCategoryLabelGenerator(new MyCategoryLabelGenerator());
plot.setDomainAxis(axis);

This is how I customize category label. (At least for chart that use CategoryAxis as domain axis..)

Rangi Lin
  • 9,303
  • 6
  • 45
  • 71
  • Looks good. Is there any (useful) way to disambiguate the result of `toString()`? – trashgod Jun 08 '12 at 02:55
  • (I'm not sure if I get what you mean) Unless JFreeChart change the implementation of creating category label from `toString()` to other method, I can't come up with good one. I don't like the `toString()` looks like now either :( – Rangi Lin Jun 08 '12 at 04:17
  • Hum... Maybe I can override `createLabel` method in `CategoryAxis` class, so it won't always use `toString()` to get text of category. – Rangi Lin Jun 08 '12 at 04:20
  • Maybe a [tooltip generator](http://stackoverflow.com/a/2875939/230513) for the more descriptive name? – trashgod Jun 08 '12 at 04:43
  • I make another attempt, this should be the better way. See my updated answer. – Rangi Lin Jun 08 '12 at 05:55