skipping labels in category plots
-
- Posts: 3
- Joined: Fri Nov 21, 2003 9:42 am
skipping labels in category plots
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
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
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
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
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


-
- Posts: 3
- Joined: Fri Nov 21, 2003 9:42 am
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
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.
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
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


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 wrote:I agree with you there...its not good to skip categories in most cases...
-
- Posts: 3
- Joined: Fri Nov 21, 2003 9:42 am
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
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
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.
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
Thanks
-
- Posts: 22
- Joined: Thu Jan 29, 2004 8:28 pm
skipping (delibrately) x-axis labels... very much needed
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
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

-
- 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
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.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.
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
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


skipping labels in category plots
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);
// 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);
CategoryPlot skipping labels redux.
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).
-- 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).
Re: skipping (delibrately) x-axis labels... very much neede
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).david.gilbert wrote: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.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.
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...
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...
-
- Posts: 6
- Joined: Sun Oct 31, 2004 10:09 pm
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?
So how do I do that?
skipping labels, continued
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.
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.