From: SourceForge.net <no...@so...> - 2008-02-25 15:42:40
|
Bugs item #1336206, was opened at 2005-10-24 12:05 Message generated for change (Settings changed) made by mhilpert You can respond by visiting: https://sourceforge.net/tracker/?func=detail&atid=115494&aid=1336206&group_id=15494 Please note that this message will contain a full copy of the comment thread, including the initial issue submission, for this request, not just the latest update. Category: General Group: None Status: Closed >Resolution: None Priority: 5 Private: No Submitted By: m.hilpert (mhilpert) Assigned to: David Gilbert (mungady) Summary: Tick units don't use date format to avoid overlapping Initial Comment: I wrote my own standard tick units with the method below. I noticed that the format of the tick label can produce overlapped labels. ------------------------------------------- /** * Create tick units for 1 explicit time period.. * * @param timePeriod A JFreeChart RegularTimePeriod class (e.g. org.jfree.data.time.Month). * @param dateFormat Optional format string to display tick labels. * @param timeZone Optional time zone (default: TimeZone.getDefault()). * @return Tick unit source for given <i>timePeriod</i>. */ public TickUnitSource createStandardTickUnits(Class timePeriod, String dateFormat, TimeZone timeZone) { TickUnits result = new TickUnits(); if (timePeriod != null) { String f = null; //default date format if (timePeriod == Hour.class) { f = "HH d-MM"; } else if (timePeriod == Day.class) { f = "d-MM-yy"; } else if (timePeriod == Week.class) { f = "w.yy"; } else if (timePeriod == Month.class) { f = "MMM-yy"; } else if (timePeriod == Quarter.class) { f = "MMM-yy"; } else if (timePeriod == Year.class) { f = "yyyy"; } if (dateFormat == null) { dateFormat = f; } if (timeZone == null) { timeZone = TimeZone.getDefault(); //default } DateFormat df = new SimpleDateFormat (dateFormat); df.setTimeZone(timeZone); if (timePeriod == Hour.class) { for (int i = 1; i <= 2400; i++) { //most labels for 100days without overlapping result.add(new DateTickUnit (DateTickUnit.HOUR, i, DateTickUnit.HOUR, 1, df)); } } else if (timePeriod == Day.class) { for (int i = 1; i <= 3650; i++) { //most labels for 10 years without overlapping result.add(new DateTickUnit (DateTickUnit.DAY, i, DateTickUnit.DAY, 1, df)); } } else if (timePeriod == Week.class) { for (int i = 1; i <= 120; i++) { //most labels for 10 years without overlapping result.add(new DateTickUnit (DateTickUnit.MONTH, i, DateTickUnit.MONTH, 1, df)); } } else if (timePeriod == Month.class) { for (int i = 1; i <= 120; i++) { //most labels for 10 years without overlapping result.add(new DateTickUnit (DateTickUnit.MONTH, i, DateTickUnit.MONTH, 1, df)); } } else if (timePeriod == Quarter.class) { result.add(new DateTickUnit (DateTickUnit.MONTH, 3, DateTickUnit.MONTH, 1, df)); //show first month of each quarter } else if (timePeriod == Year.class) { for (int i = 1; i <= 100; i++) { //most labels for 100 years without overlapping result.add(new DateTickUnit (DateTickUnit.YEAR, i, DateTickUnit.YEAR, 1, df)); } } }//else: input unavailable return result; }//createStandardTickUnits() ------------------------------ I use this method for an example chart: ----------------------- /** * Chart with time series for x axis. * * Tested with JFreeChart 1.0.0-rc1 * * @return JFreeChart. */ private JFreeChart testDateAxis2() { JFreeChart result = null; TimeSeriesCollection ds = new TimeSeriesCollection(); TimeSeries ts = new TimeSeries ("series1", "dates", "amounts", Month.class); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd"); sdf.setLenient(false); //strict parsing ts.add(new Month(sdf.parse("2003-12-31", new ParsePosition(0))), new BigDecimal(100.0)); ts.add(new Month(sdf.parse("2004-01-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2004-02-29", new ParsePosition(0))), new BigDecimal(104.0)); ts.add(new Month(sdf.parse("2004-03-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2004-04-30", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2004-05-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2004-06-30", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2004-07-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2004-08-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2004-09-30", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2004-10-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2004-11-30", new ParsePosition(0))), new BigDecimal(100.0)); ts.add(new Month(sdf.parse("2004-12-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-01-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-02-28", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-03-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-04-30", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-05-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-06-30", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-07-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-08-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-09-30", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-10-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2005-11-30", new ParsePosition(0))), new BigDecimal(100.0)); ts.add(new Month(sdf.parse("2005-12-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-01-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-02-28", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-03-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-04-30", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-05-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-06-30", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-07-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-08-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-09-30", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-10-31", new ParsePosition(0))), new BigDecimal(106.0)); ts.add(new Month(sdf.parse("2006-11-30", new ParsePosition(0))), new BigDecimal(100.0)); ts.add(new Month(sdf.parse("2006-12-31", new ParsePosition(0))), new BigDecimal(106.0)); ds.addSeries(ts); result = ChartFactory.createTimeSeriesChart ("Time Series Chart", "X", "Y", ds, true, false, false); //set date format that causes the trouble: XYPlot xyPlot = result.getXYPlot(); DateAxis da = (DateAxis) xyPlot.getDomainAxis(); da.setStandardTickUnits(createStandardTickUnits (Hour.class, null, null)); return result; }//testDateAxis2() ---------------------------------------------------- the result is that all tick labels overlap. If I just change the format for hour from "HH d-MM" to "HH" the labels don't overlap anymore. Hence, the label format can produce overlapped labels. ---------------------------------------------------------------------- >Comment By: m.hilpert (mhilpert) Date: 2008-02-25 16:42 Message: Logged In: YES user_id=667728 Originator: YES I can't agree with you: "The downside of the look-up table approach is that there can only be a finite number of tick sizes (and associated label formats) to choose from - but the default table in the DateAxis class handles the ranges from a few milliseconds right up to a few centuries, so that usually isn't a practical problem for date axes." In my case, I have a chart with a time span of 10 years (=> much less than a few centuries), and it's open to any size of time span. So this "endless" creation of DateTickUnits is pretty useless just to avoid overlapping. Every workaround I write is just doomed to break after some time as another example has an even wider time range and the labels overlapp again. So I keep on adding more and more DateTickUnits, keep on instantiating more objects, which can't be good solution just to avoid label overlapping. My current code is: -------------------------------------------------------------------- /** * Create tick units for a time period (e.g. Month.class) and date format (e.g. "MMM yyyy"). * * @param timePeriod A JFreeChart RegularTimePeriod class (e.g. org.jfree.data.time.Month). * @param dateFormat Optional format string to display tick labels (e.g. "MMM yyyy"). * @param timeZone Optional time zone (default: TimeZone.getDefault()). * @return Tick unit source for given <i>timePeriod</i>. */ @SuppressWarnings("unchecked") static public final TickUnitSource createStandardTickUnits(final Class<?> timePeriod, String dateFormat, TimeZone timeZone) { final TickUnits result = new TickUnits(); if (timePeriod != null) { String f = null; //default date format if (timePeriod == Hour.class) { f = "HH d-MM"; } else if (timePeriod == Day.class) { f = "d-MM-yy"; } else if (timePeriod == Week.class) { f = "w-yy"; } else if (timePeriod == Month.class) { f = "MM-yy"; } else if (timePeriod == Quarter.class) { f = "MM-yy"; } else if (timePeriod == Year.class) { f = "yyyy"; } if (dateFormat == null) { dateFormat = f; } if (timeZone == null) { timeZone = TimeZone.getDefault(); //default } final DateFormat df = new SimpleDateFormat(dateFormat); df.setTimeZone(timeZone); if (timePeriod == Hour.class) { for (int i = 1; i <= 24; i++) { result.add(new DateTickUnit(DateTickUnit.HOUR, i, DateTickUnit.HOUR, 1, df)); } } else if (timePeriod == Day.class) { for (int i = 1; i <= 31; i++) { result.add(new DateTickUnit(DateTickUnit.DAY, i, DateTickUnit.DAY, 1, df)); } } else if (timePeriod == Week.class) { for (int i = 1; i <= 56; i++) { result.add(new DateTickUnit(DateTickUnit.MONTH, i, DateTickUnit.MONTH, 1, df)); } } else if (timePeriod == Month.class) { for (int i = 1; i <= 120; i++) { //span 10 years to avoid overlapping result.add(new DateTickUnit(DateTickUnit.MONTH, i, DateTickUnit.MONTH, 1, df)); } } else if (timePeriod == Quarter.class) { for (int i = 1; i <= 4; i++) { result.add(new DateTickUnit(DateTickUnit.YEAR, i, DateTickUnit.MONTH, 4, df)); } } else if (timePeriod == Year.class) { result.add(new DateTickUnit(DateTickUnit.YEAR, 1, DateTickUnit.YEAR, 1, df)); result.add(new DateTickUnit(DateTickUnit.YEAR, 2, DateTickUnit.YEAR, 1, df)); result.add(new DateTickUnit(DateTickUnit.YEAR, 5, DateTickUnit.YEAR, 1, df)); result.add(new DateTickUnit(DateTickUnit.YEAR, 10, DateTickUnit.YEAR, 1, df)); result.add(new DateTickUnit(DateTickUnit.YEAR, 25, DateTickUnit.YEAR, 5, df)); result.add(new DateTickUnit(DateTickUnit.YEAR, 50, DateTickUnit.YEAR, 10, df)); result.add(new DateTickUnit(DateTickUnit.YEAR, 100, DateTickUnit.YEAR, 20, df)); } }//else: input unavailable return result; }//createStandardTickUnits() ---------------------------------------------------------------------------------- ---------------------------------------------------------------------- Comment By: David Gilbert (mungady) Date: 2005-11-03 13:31 Message: Logged In: YES user_id=112975 The axis finds tick units through the TickUnitSource interface, so you can always write your own class that implements this (see StandardTickUnitSource for an example that works for a NumberAxis). Calculating an appropriate tick size isn't that hard, even for dates - the hard part is choosing a formatter for the tick labels. That's why JFreeChart uses a look-up table approach, so that the formatter can be tailored according to the particular tick size. It is completely customisable, since you can replace the look up table. The downside of the look-up table approach is that there can only be a finite number of tick sizes (and associated label formats) to choose from - but the default table in the DateAxis class handles the ranges from a few milliseconds right up to a few centuries, so that usually isn't a practical problem for date axes. Regards, Dave Gilbert JFreeChart Project Leader ---------------------------------------------------------------------- Comment By: m.hilpert (mhilpert) Date: 2005-11-03 13:06 Message: Logged In: YES user_id=667728 I understand the algo now, but this is not enough for generic charts. You will always have another case where a fixed set of such units is not enough or not optimal. Every once in a while I get an error report from our end users, that a chart shows "bad" axis lables: too less, too many, overlapping, too much space after the right last label, left first label too much to the left, etc. And these are reports that just occur for some charts (with the same chart definition, but other data to display). So, I'm looking for a general solution that is bullet proof for any amount of data items - very few, some, many, and a large set. ---------------------------------------------------------------------- Comment By: David Gilbert (mungady) Date: 2005-11-02 15:41 Message: Logged In: YES user_id=112975 All that is happening here is that JFreeChart keeps looking through your custom collection of tick units for larger and larger tick units to avoid the overlapping labels. It gets to the largest tick unit you've specified (2400 hours or 100 days, resulting in 3 or 4 tick marks per year) and is forced to use that since there is no larger tick unit. Depending on the chart size, this can result in overlapping labels. The solution is to add some larger tick units to your custom collection, appropriate for the range of data values you are displaying. Another thing I noticed is that you've specified *every* unit between 1 and 2400 hours. That's allowed, but probably overkill. Who's going to notice the difference between tick marks at every 2400 hours along the axis, versus every 2399 hours along the axis, versus every 2398 hours, and so on? It probably makes more sense to choose various multiples (100 hours, 200 hours, 500 hours, 1000 hours, 2000 hours). Regards, Dave Gilbert JFreeChart Project Leader ---------------------------------------------------------------------- You can respond by visiting: https://sourceforge.net/tracker/?func=detail&atid=115494&aid=1336206&group_id=15494 |