skipping labels in category plots

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
chris.adams
Posts: 3
Joined: Fri Nov 21, 2003 9:42 am

skipping labels in category plots

Post by chris.adams » Fri Nov 21, 2003 9:44 am

I'm creating a bar chart with about 30 bars, and the labels overlap.

I want to skip out every other label, but obviously can't set the label to " " as the category names need to be unique.

I noticed this has been asked for before and DG said some code had been submitted into CVS to address this...any one have any idea how I can use it?

thanks

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Fri Nov 21, 2003 10:15 am

This feature has been "broken" in the 0.9.14 release, because of other changes I made to the category axis labelling. It might get reinstated in a future release, although to be honest I've never liked this feature - by hiding some category labels, you are presenting a chart that is incomplete and your users won't know what the data means.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

chris.adams
Posts: 3
Joined: Fri Nov 21, 2003 9:42 am

Post by chris.adams » Fri Nov 21, 2003 10:30 am

I agree with you there...its not good to skip categories in most cases.

However, I guess what I really need then is a horizontal stacked time series bar chart...is this possible?

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Fri Nov 21, 2003 10:46 am

OK, I understand what you mean. Stacked charts with time series data is something that JFreeChart can't handle easily at this point, partly because the XYDataset interface doesn't enforce a "tabular" structure for the data (it allows each series to have a different set of x-values).

Richard Atkinson has written a StackedAreaXYRenderer class that works on the assumption that all series share the same x-values. You could follow the same approach to implement a stacked bar renderer.

Alternatively, you could stick with the CategoryPlot solution but look at drawing the categories on the axis at an angle to prevent overlapping, something similar to what is shown in BarChart3DDemo1.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

Matthias
Posts: 42
Joined: Fri Apr 18, 2003 9:49 am
Location: Germany

Post by Matthias » Mon Nov 24, 2003 3:17 pm

chris.adams wrote:I agree with you there...its not good to skip categories in most cases...
Why not? Skipping labels looks much better than overlapping labels and when you can zoom in so that the initially skipped labels appear or/and if you have their values in the tooltips it is not such a bad solution, imho - at least a useful option.

chris.adams
Posts: 3
Joined: Fri Nov 21, 2003 9:42 am

Post by chris.adams » Mon Nov 24, 2003 3:31 pm

The reason it doesn't make sense is because this is a category plot we are talking about.

Now of course its possible that your categories represent quantities, or years, or some other numeric data, in which case skipping labels makes sense, as a reasonably intelligent user will automatically interpolate and "fill-in" the missing values.

But it makes no sense to skip labels when you have discreet categories not based on numbers - e.g if my categories were "dog" "sheep" and "cow" then it makes no sense to look at a chart where only the "dog" and "cow" labels are present, as you would have no idea what the 3rd column is meant to be.

So I think DG is entirely correct in saying that you probably should not be allowed to skip labels. After all, if you've got a numeric Domain axis then use a TimeSeriesBarChart. I'd just like to re-iterate that in my case, I need a vertical stacked TimeSeriesBarChart, which doesn't exist in the current package

Matthias
Posts: 42
Joined: Fri Apr 18, 2003 9:49 am
Location: Germany

Post by Matthias » Tue Nov 25, 2003 2:25 pm

I don't want to extend this discussion unnecessarily but just want to add that i was aware that you were talking about category labels but nonetheless can't follow your argumentation why skipping labels should not be allowed/make sense. Even if interpolating values is not possible it still makes more sense (to me) to have some labels that i can read than many which i can not read since they overlap (and look awful). Of course, it may be that in your special case a stacked TimeSeriesBarChart would solve the problem in a better way, but this is no general solution.

Stacey

Post by Stacey » Tue Jan 27, 2004 2:55 pm

I agree with the idea that skipping labels may not make sense for categories, but allowing the USER to determine the usefulness of skipping labels seems like the best option. I too use Category Bar charts to represent "timed" data. This is because the renderers for TimeSereis do not return a useful chart for my application. It seemed as though the skipCategoriesToFit functionality was working well in previous releases, and many users had taken advantage of it to customize their applications. I would LOVE if the skipping categories options was made available again.
Thanks

phamdinhnguyen
Posts: 22
Joined: Thu Jan 29, 2004 8:28 pm

skipping (delibrately) x-axis labels... very much needed

Post by phamdinhnguyen » Mon Mar 01, 2004 8:22 pm

Needed back then, and still needed now!!!

My chart display total number of transaction over a period of time.
A case in point, domain values from midnight to noon: 0000 and 1200, representing as category "0000" through "1200", with 2 minutes between x-values, there are 360 category labels in a chart size of 800x600.

Deliberately skipping category labels in this instance, not only makes the category labels readable, but also perfectly preserves the chart's usefulness.

Please consider this very practical need, and make it available again.
... And quick :wink:

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Re: skipping (delibrately) x-axis labels... very much neede

Post by david.gilbert » Tue Mar 02, 2004 5:12 pm

phamdinhnguyen wrote:My chart display total number of transaction over a period of time.
A case in point, domain values from midnight to noon: 0000 and 1200, representing as category "0000" through "1200", with 2 minutes between x-values, there are 360 category labels in a chart size of 800x600.
Is there some reason why a time series chart won't work? I have no plans to reintroduce the label skipping facility, I'd rather put my time into filling any gaps in the time series charts.

If you *really* want a category axis that skips some categories, JFreeChart has been designed so that you can swap in your own axis implementation...
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

guest

skipping labels in category plots

Post by guest » Tue Jul 06, 2004 8:08 am

JFreeChart 0.9.13

// customise the domain axis...
CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setVerticalCategoryLabels(true);
domainAxis.setSkipCategoryLabelsToFit(true);

JFreeChart 0.9.20

// create categories label...
String[] categories = new String[100];
for (int i=0; i<categories.length; i++) {
categories = "Data"+String.valueOf(i);
}
// prepare for skip label categories...
String[] extendedCategories = new String[ categories.length ];
for( int i=0; i< categories.length; i++ )
{
if( i%5 == 0 )
extendedCategories = categories;
else
extendedCategories = " ";
}

... produce chart ....

// get a reference to the plot for further customisation...
CategoryPlot plot = chart.getCategoryPlot();

// set skip label categories...
final ExtendedCategoryAxis extendedCategoryAxis = new ExtendedCategoryAxis(null);
for (int i=0; i<categories.length; i++) {
extendedCategoryAxis.addSubLabel(categories, extendedCategories);
}
Font theFont = extendedCategoryAxis.getTickLabelFont();
extendedCategoryAxis.setTickLabelFont(new Font("Arial", Font.PLAIN, 0));
extendedCategoryAxis.setSubLabelFont(theFont);
plot.setDomainAxis(extendedCategoryAxis);

markhail
Posts: 2
Joined: Tue Feb 08, 2005 11:02 pm
Contact:

CategoryPlot skipping labels redux.

Post by markhail » Wed Feb 09, 2005 5:34 pm

I have been trying to finesse the "skip label" problem like so:

-- define MyCategoryAxis as "extends CategoryAxis" with a constructor

MyCategoryAxis mca = new MyCategoryAxis(aCategoryAxis)

with this setup

this.lowerMargin = ca.getLowerMargin();
this.upperMargin = ca.getUpperMargin();
this.categoryMargin = ca.getCategoryMargin();
this.maxCategoryLabelLines = ca.getMaxCategoryLabelLines();
this.maxCategoryLabelWidthRatio = ca.getMaxCategoryLabelWidthRatio();

setTickMarksVisible(false); // not supported by this axis type yet

this.categoryLabelPositionOffset = ca.getCategoryLabelPositionOffset();
this.categoryLabelPositions = ca.getCategoryLabelPositions();
this.categoryLabelToolTips = new HashMap();

had to redefine all the "this." variables due to access issues.


-- redefining the "refreshTicks" method by inserting the following code

// previous lines were while (iterator.hasNext() )
// Comparable category = (Comparable) iterator.next();

if (tickTock == 0) {
label = createLabel(category, l * r, edge, g2);
System.out.println(label.getClass().toString());
List ll = label.getLines();
Iterator ill = ll.iterator();
while (ill.hasNext())
{
System.out.println(((TextLine) ill.next()).getFirstTextFragment().getText()+ " "
+ tickTock + " "
+ this.getClass().toString());
}
}
else {
label = createLabel(" ",
l * r, edge, g2);
System.out.println(label.getClass().toString());
List ll = label.getLines();
Iterator ill = ll.iterator();
while (ill.hasNext())
{
System.out.println(((TextLine) ill.next()).getFirstTextFragment().getText() + " "
+ tickTock + " " + this.getClass().toString());
}

}

System.out.println("tickTock = " + tickTock);
System.out.println("Category: "
+ category + " Label : " +
label.getLastLine().getFirstTextFragment().getText() + "<>");

label is declared as a TextBlock just inside the "refreshTicks" method, so I don't think there are scoping issues.

What I get is this from the trace output:

class org.jfree.text.TextBlock
... 0 class smithling.MyCategoryAxis
tickTock = 0
Category: 00:30 Label : ...<>

class org.jfree.text.TextBlock
... 1 class smithling.MyCategoryAxis
tickTock = 1
Category: 00:45 Label : ...<>

class org.jfree.text.TextBlock
... 2 class smithling.MyCategoryAxis
tickTock = 2
Category: 01:00 Label : ...<>

class org.jfree.text.TextBlock
... 3 class smithling.MyCategoryAxis
tickTock = 3
Category: 01:15 Label : ...<>

In other words the Label does not seem to be getting the category data. What am I (not) doing that would cause this ? (Reverting to the CategoryAxis gives me the continuous labels).

Guest

Re: skipping (delibrately) x-axis labels... very much neede

Post by Guest » Thu Feb 17, 2005 9:03 pm

david.gilbert wrote:
phamdinhnguyen wrote:My chart display total number of transaction over a period of time.
A case in point, domain values from midnight to noon: 0000 and 1200, representing as category "0000" through "1200", with 2 minutes between x-values, there are 360 category labels in a chart size of 800x600.
Is there some reason why a time series chart won't work? I have no plans to reintroduce the label skipping facility, I'd rather put my time into filling any gaps in the time series charts.

If you *really* want a category axis that skips some categories, JFreeChart has been designed so that you can swap in your own axis implementation...
So has anyone actually done this? Seems that since a lot of people really want this feature someone has probably gone ahead and done it for themselves. I have no idea how much effort it would take but currently I don't have time to even find that out. In the meantime I'll have to convince my project manager that we are equally well served with using a TimeSeriesBarchart for our potentially very large dataset (over 500 dates).

Other than this particular issue I am very happy that JFreeChart exists. If we where to implement the charting ourselves for our application we would have to spend months on just the charts...

Jonteponte
Posts: 6
Joined: Sun Oct 31, 2004 10:09 pm

Post by Jonteponte » Fri Feb 18, 2005 9:43 am

It seems my project manager does not like the TimeSeriesXYchart i offered him instead. It has to be the stackedbarchart. So I'm back to the same problem again... And I have a couple of hours to fix it.... An intermediate solution would be to make the text for the dates smaller (very small) so they won't overlap as much and maybe, just maybe can be read by someone with excellent sight....

So how do I do that?

markhail
Posts: 2
Joined: Tue Feb 08, 2005 11:02 pm
Contact:

skipping labels, continued

Post by markhail » Fri Feb 18, 2005 5:28 pm

That was what I was trying to do.

What I found out is that the computation of r*l for a label width is not working in my code. Hard-coding in what I found in the debugger works but leaves me wondering whatintheheck is happening.

Here is where I went with this....

public class MJSCategoryAxis extends CategoryAxis
{

double lowerMargin;
double upperMargin;
double categoryMargin;
int maxCategoryLabelLines;
float maxCategoryLabelWidthRatio;
int categoryLabelPositionOffset;
CategoryLabelPositions categoryLabelPositions;
Map categoryLabelToolTips;

public MJSCategoryAxis()
{
super();
}

public MJSCategoryAxis(String xlabel)
{
super(xlabel);
}

public MJSCategoryAxis(CategoryAxis ca)
{
this.lowerMargin = ca.getLowerMargin();
this.upperMargin = ca.getUpperMargin();
this.categoryMargin = ca.getCategoryMargin();
this.maxCategoryLabelLines = ca.getMaxCategoryLabelLines();
this.maxCategoryLabelWidthRatio = ca.getMaxCategoryLabelWidthRatio();

setTickMarksVisible(false); // not supported by this axis type yet

this.categoryLabelPositionOffset = ca.getCategoryLabelPositionOffset();
this.categoryLabelPositions = ca.getCategoryLabelPositions();
this.categoryLabelToolTips = new HashMap();


}
/**
* Creates a temporary list of ticks that can be used when drawing the axis.
*
* @param g2 the graphics device (used to get font measurements).
* @param state the axis state.
* @param plotArea the area where the plot and axes will be drawn.
* @param dataArea the area inside the axes.
* @param edge the location of the axis.
*
* @return A list of ticks.
*/

public List refreshTicks(Graphics2D g2,
AxisState state,
Rectangle2D plotArea,
Rectangle2D dataArea,
RectangleEdge edge) {

TextBlock label=null;

List ticks = new java.util.ArrayList();

// sanity check for data area...
if (dataArea.getHeight() <= 0.0 || dataArea.getWidth() < 0.0) {
return ticks;
}

CategoryPlot plot = (CategoryPlot) getPlot();
List categories = plot.getCategories();
double max = 0.0;

if (categories != null) {
CategoryLabelPosition position = this.categoryLabelPositions.getLabelPosition(edge);
float r = this.maxCategoryLabelWidthRatio;
if (r <= 0.0) {
r = position.getWidthRatio();
}

float l = 0.0f;
if (position.getWidthType() == CategoryLabelWidthType.CATEGORY) {
l = (float) calculateCategorySize(categories.size(), dataArea, edge);
}
else {
if (RectangleEdge.isLeftOrRight(edge)) {
l = (float) dataArea.getWidth();
}
else {
l = (float) dataArea.getHeight();
}
}
int categoryIndex = 0;
int tickTock = 0;
Iterator iterator = categories.iterator();
while (iterator.hasNext()) {
Comparable category = (Comparable) iterator.next();
// the one liner......

if (tickTock == 0) {
label = createLabel(category,
// l * r,
254.4f,
edge, g2);
System.out.println(label.getClass().toString());
List ll = label.getLines();
Iterator ill = ll.iterator();
while (ill.hasNext())
{
System.out.println(((TextLine) ill.next()).getFirstTextFragment().getText()+ " "
+ tickTock + " "
+ this.getClass().toString());
}
}
else {
label = createLabel((Comparable) " ",
// l * r,
254.4f,
edge, g2);
System.out.println(label.getClass().toString());
List ll = label.getLines();
Iterator ill = ll.iterator();
while (ill.hasNext())
{
System.out.println(((TextLine) ill.next()).getFirstTextFragment().getText() + " "
+ tickTock + " " + this.getClass().toString());
}

}


if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
max = Math.max(max, calculateTextBlockHeight(label, position, g2));
}

else if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) {
max = Math.max(max, calculateTextBlockWidth(label, position, g2));
}
Tick tick = new CategoryTick(
category, label,
position.getLabelAnchor(), position.getRotationAnchor(), position.getAngle()
);
ticks.add(tick);
categoryIndex = categoryIndex + 1;
tickTock = ++tickTock % 4;
}
}
state.setMax(max);
return ticks;

}

}

254.4 is what I get when I trace through the JFree code for CategoryAxis. I was getting 5.9 in the subclass which is about a factor of 50 too small. Where 254.4 comes from is a mystery to me.

A cleaner implementation would be to have the modulo divisor that is operating against "tickTock" be a private variable and allow it to be controlled by a getter/setter. Now that I've had some time to scream about this, I'm changing it in my code.

Locked