From c92fe4cea95b44bce75dde486654fa5843f125c2 Mon Sep 17 00:00:00 2001
From: Tim Molter <tim.molter@gmail.com>
Date: Sun, 17 Feb 2013 21:54:18 +0100
Subject: [PATCH] another rearrange

---
 .../com/xeiam/xchart/demo/XChartDemo.java     |   4 -
 .../DateChart01.java}                         |  27 ++--
 .../xchart/demo/charts/line/LineChart09.java  |   6 +-
 .../AxisTickCalculator.java                   | 104 +++++++++----
 .../DateAxisTickCalculator.java               | 144 +++++++++++-------
 .../axistickcalculator/DateFormatter.java     | 127 ---------------
 .../NumberAxisTickCalculator.java             |  30 ++++
 .../axistickcalculator/NumberFormatter.java   | 108 -------------
 .../com/xeiam/xchart/style/StyleManager.java  |  91 +++++++++--
 .../xeiam/xchart/unit/ValueFormatterTest.java |  71 ++++-----
 10 files changed, 324 insertions(+), 388 deletions(-)
 rename xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/{line/LineChart04.java => date/DateChart01.java} (73%)
 delete mode 100644 xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateFormatter.java
 delete mode 100644 xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/NumberFormatter.java

diff --git a/xchart-demo/src/main/java/com/xeiam/xchart/demo/XChartDemo.java b/xchart-demo/src/main/java/com/xeiam/xchart/demo/XChartDemo.java
index 6c15a09a..f906f95a 100644
--- a/xchart-demo/src/main/java/com/xeiam/xchart/demo/XChartDemo.java
+++ b/xchart-demo/src/main/java/com/xeiam/xchart/demo/XChartDemo.java
@@ -33,7 +33,6 @@ import com.xeiam.xchart.demo.charts.area.AreaChart01;
 import com.xeiam.xchart.demo.charts.line.LineChart01;
 import com.xeiam.xchart.demo.charts.line.LineChart02;
 import com.xeiam.xchart.demo.charts.line.LineChart03;
-import com.xeiam.xchart.demo.charts.line.LineChart04;
 import com.xeiam.xchart.demo.charts.line.LineChart05;
 import com.xeiam.xchart.demo.charts.line.LineChart06;
 import com.xeiam.xchart.demo.charts.line.LineChart07;
@@ -140,9 +139,6 @@ public class XChartDemo extends JPanel implements TreeSelectionListener {
     chart = new DefaultMutableTreeNode(new ChartInfo("LineChart03 - Multiple curves on one Chart", new LineChart03().getChart()));
     category.add(chart);
 
-    chart = new DefaultMutableTreeNode(new ChartInfo("LineChart04 - Date Axis", new LineChart04().getChart()));
-    category.add(chart);
-
     chart = new DefaultMutableTreeNode(new ChartInfo("LineChart05 - Vertical and horizontal lines", new LineChart05().getChart()));
     category.add(chart);
 
diff --git a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart04.java b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/date/DateChart01.java
similarity index 73%
rename from xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart04.java
rename to xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/date/DateChart01.java
index fa954642..f1234c92 100644
--- a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart04.java
+++ b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/date/DateChart01.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.xeiam.xchart.demo.charts.line;
+package com.xeiam.xchart.demo.charts.date;
 
 import java.text.DateFormat;
 import java.text.ParseException;
@@ -25,22 +25,15 @@ import java.util.TimeZone;
 
 import com.xeiam.xchart.Chart;
 import com.xeiam.xchart.SwingWrapper;
+import com.xeiam.xchart.demo.charts.line.ExampleChart;
 import com.xeiam.xchart.style.Series;
 import com.xeiam.xchart.style.StyleManager.LegendPosition;
 
-/**
- * Date Axis
- * <p>
- * Demonstrates the following:
- * <ul>
- * <li>Date-tyoe X Axis Data
- * <li>Placing legend inside northwest corner of plot area
- */
-public class LineChart04 implements ExampleChart {
+public class DateChart01 implements ExampleChart {
 
   public static void main(String[] args) {
 
-    ExampleChart exampleChart = new LineChart04();
+    ExampleChart exampleChart = new DateChart01();
     Chart chart = exampleChart.getChart();
     new SwingWrapper(chart).displayChart();
   }
@@ -55,12 +48,12 @@ public class LineChart04 implements ExampleChart {
     Collection<Date> xData = new ArrayList<Date>();
     Collection<Number> yData = new ArrayList<Number>();
 
-    DateFormat sdf = new SimpleDateFormat("yyyy.MM.dd.HH");
+    DateFormat sdf = new SimpleDateFormat("ss:S");
     Date date = null;
     for (int i = 1; i <= 10; i++) {
 
       try {
-        date = sdf.parse("2012.12.22." + (10 + i));
+        date = sdf.parse("31:" + (100 * i));
       } catch (ParseException e) {
         e.printStackTrace();
       }
@@ -69,15 +62,13 @@ public class LineChart04 implements ExampleChart {
     }
 
     // Customize Chart
-    chart.setChartTitle("LineChart04");
-    chart.setXAxisTitle("time of day");
-    chart.setYAxisTitle("gigawatts");
-    chart.getStyleManager().getDateFormatter().setTimezone(TimeZone.getTimeZone("UTC"));
+    chart.setChartTitle("DateChart01");
+    chart.getStyleManager().setTimezone(TimeZone.getTimeZone("UTC"));
     chart.getStyleManager().setLegendPosition(LegendPosition.InsideNW);
 
     Series series = chart.addDateSeries("value", xData, yData);
 
     return chart;
-  }
 
+  }
 }
diff --git a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart09.java b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart09.java
index 39348ac9..c91f5353 100644
--- a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart09.java
+++ b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart09.java
@@ -81,9 +81,9 @@ public class LineChart09 implements ExampleChart {
     chart.getStyleManager().setLegendFont(new Font(Font.SERIF, Font.PLAIN, 18));
     chart.getStyleManager().setAxisTitleFont(new Font(Font.SANS_SERIF, Font.ITALIC, 18));
     chart.getStyleManager().setAxisTickLabelsFont(new Font(Font.SERIF, Font.PLAIN, 11));
-    chart.getStyleManager().getDateFormatter().setDatePattern("dd-MMM");
-    chart.getStyleManager().getDecimalFormatter().setNormalDecimalPattern("#.000");
-    chart.getStyleManager().getDateFormatter().setLocale(Locale.GERMAN);
+    chart.getStyleManager().setDatePattern("dd-MMM");
+    chart.getStyleManager().setNormalDecimalPattern("#.000");
+    chart.getStyleManager().setLocale(Locale.GERMAN);
 
     Series series = chart.addDateSeries("Fake Data", xData, yData);
     series.setLineColor(SeriesColor.BLUE);
diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/AxisTickCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/AxisTickCalculator.java
index 35bf74aa..efacecbd 100644
--- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/AxisTickCalculator.java
+++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/AxisTickCalculator.java
@@ -22,12 +22,13 @@
 package com.xeiam.xchart.internal.chartpart.axistickcalculator;
 
 import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
 import java.util.LinkedList;
 import java.util.List;
 
 import com.xeiam.xchart.internal.chartpart.Axis.AxisType;
 import com.xeiam.xchart.internal.chartpart.Axis.Direction;
-import com.xeiam.xchart.internal.chartpart.AxisPair;
 import com.xeiam.xchart.style.StyleManager;
 
 /**
@@ -65,43 +66,90 @@ public abstract class AxisTickCalculator {
     this.maxValue = maxValue;
     this.styleManager = styleManager;
 
-    calculate();
   }
 
-  private void calculate() {
-
-    // a check if all axis data are the exact same values
-    if (minValue == maxValue) {
-      if (getAxisType() == AxisType.Number) {
-        tickLabels.add(styleManager.getDecimalFormatter().formatNumber(maxValue));
-      } else if (getAxisType() == AxisType.Date) {
-        tickLabels.add(styleManager.getDateFormatter().formatDateValue(maxValue, maxValue, maxValue));
+  BigDecimal getGridStepDecimal(double gridStepHint) {
+
+    // gridStepHint --> significand * 10 ** exponent
+    // e.g. 724.1 --> 7.241 * 10 ** 2
+    double significand = gridStepHint;
+    int exponent = 0;
+    if (significand == 0) {
+      exponent = 1;
+    } else if (significand < 1) {
+      while (significand < 1) {
+        significand *= 10.0;
+        exponent--;
+      }
+    } else {
+      while (significand >= 10) {
+        significand /= 10.0;
+        exponent++;
       }
-      tickLocations.add((int) (workingSpace / 2.0));
-      return;
     }
 
-    // tick space - a percentage of the working space available for ticks, i.e. 95%
-    int tickSpace = AxisPair.getTickSpace(workingSpace); // in plot space
+    // calculate the grid step with hint.
+    BigDecimal gridStep;
+    if (significand > 7.5) {
+      // gridStep = 10.0 * 10 ** exponent
+      gridStep = BigDecimal.TEN.multiply(pow(10, exponent));
+    } else if (significand > 3.5) {
+      // gridStep = 5.0 * 10 ** exponent
+      gridStep = new BigDecimal(new Double(5).toString()).multiply(pow(10, exponent));
+    } else if (significand > 1.5) {
+      // gridStep = 2.0 * 10 ** exponent
+      gridStep = new BigDecimal(new Double(2).toString()).multiply(pow(10, exponent));
+    } else {
+      // gridStep = 1.0 * 10 ** exponent
+      gridStep = pow(10, exponent);
+    }
+    return gridStep;
+  }
 
-    // where the tick should begin in the working space in pixels
-    int margin = AxisPair.getTickStartOffset(workingSpace, tickSpace); // in plot space BigDecimal gridStep = getGridStepForDecimal(tickSpace);
+  /**
+   * Calculates the value of the first argument raised to the power of the second argument.
+   * 
+   * @param base the base
+   * @param exponent the exponent
+   * @return the value <tt>a<sup>b</sup></tt> in <tt>BigDecimal</tt>
+   */
+  private BigDecimal pow(double base, int exponent) {
+
+    BigDecimal value;
+    if (exponent > 0) {
+      value = new BigDecimal(new Double(base).toString()).pow(exponent);
+    } else {
+      value = BigDecimal.ONE.divide(new BigDecimal(new Double(base).toString()).pow(-exponent));
+    }
+    return value;
+  }
 
-    BigDecimal gridStep = getGridStep(tickSpace);
-    BigDecimal firstPosition = getFirstPosition(minValue, gridStep);
+  /**
+   * Format a number value, if the override patterns are null, it uses defaults
+   * 
+   * @param value
+   * @return
+   */
+  public String formatNumber(BigDecimal value) {
 
-    // generate all tickLabels and tickLocations from the first to last position
-    for (BigDecimal tickPosition = firstPosition; tickPosition.compareTo(maxValue) <= 0; tickPosition = tickPosition.add(gridStep)) {
+    NumberFormat numberFormat = NumberFormat.getNumberInstance(styleManager.getLocale());
+
+    BigDecimal absoluteValue = value.abs();
+
+    if (absoluteValue.compareTo(new BigDecimal("10000.000001")) == -1 && absoluteValue.compareTo(new BigDecimal(".0009999999")) == 1 || BigDecimal.ZERO.compareTo(value) == 0) {
+
+      DecimalFormat normalFormat = (DecimalFormat) numberFormat;
+      normalFormat.applyPattern(styleManager.getNormalDecimalPattern());
+      return normalFormat.format(value);
+
+    } else {
+
+      DecimalFormat scientificFormat = (DecimalFormat) numberFormat;
+      scientificFormat.applyPattern(styleManager.getScientificDecimalPattern());
+      return scientificFormat.format(value);
 
-      if (getAxisType() == AxisType.Number) {
-        tickLabels.add(styleManager.getDecimalFormatter().formatNumber(tickPosition));
-      } else if (getAxisType() == AxisType.Date) {
-        tickLabels.add(styleManager.getDateFormatter().formatDateValue(tickPosition, minValue, maxValue));
-      }
-      // here we convert tickPosition finally to plot space, i.e. pixels
-      int tickLabelPosition = (int) (margin + ((tickPosition.subtract(minValue)).doubleValue() / (maxValue.subtract(minValue)).doubleValue() * tickSpace));
-      tickLocations.add(tickLabelPosition);
     }
+
   }
 
   public List<Integer> getTickLocations() {
diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateAxisTickCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateAxisTickCalculator.java
index b007ff60..58ef2836 100644
--- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateAxisTickCalculator.java
+++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateAxisTickCalculator.java
@@ -22,9 +22,12 @@
 package com.xeiam.xchart.internal.chartpart.axistickcalculator;
 
 import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.concurrent.TimeUnit;
 
 import com.xeiam.xchart.internal.chartpart.Axis.AxisType;
 import com.xeiam.xchart.internal.chartpart.Axis.Direction;
+import com.xeiam.xchart.internal.chartpart.AxisPair;
 import com.xeiam.xchart.style.StyleManager;
 
 /**
@@ -34,6 +37,14 @@ import com.xeiam.xchart.style.StyleManager;
  */
 public class DateAxisTickCalculator extends AxisTickCalculator {
 
+  public static final long SEC_SCALE = TimeUnit.SECONDS.toMillis(1L);
+  public static final long MIN_SCALE = TimeUnit.MINUTES.toMillis(1L);
+  public static final long HOUR_SCALE = TimeUnit.HOURS.toMillis(1L);
+  public static final long DAY_SCALE = TimeUnit.DAYS.toMillis(1L);
+  public static final long WEEK_SCALE = TimeUnit.DAYS.toMillis(1L) * 7;
+  public static final long MONTH_SCALE = TimeUnit.DAYS.toMillis(1L) * 31;
+  public static final long YEAR_SCALE = TimeUnit.DAYS.toMillis(1L) * 365;
+
   /**
    * Constructor
    * 
@@ -46,7 +57,35 @@ public class DateAxisTickCalculator extends AxisTickCalculator {
   public DateAxisTickCalculator(Direction axisDirection, int workingSpace, BigDecimal minValue, BigDecimal maxValue, StyleManager styleManager) {
 
     super(axisDirection, workingSpace, minValue, maxValue, styleManager);
+    calculate();
+  }
+
+  private void calculate() {
+
+    // a check if all axis data are the exact same values
+    if (minValue == maxValue) {
+      tickLabels.add(formatDateValue(maxValue, maxValue, maxValue));
+      tickLocations.add((int) (workingSpace / 2.0));
+      return;
+    }
+
+    // tick space - a percentage of the working space available for ticks, i.e. 95%
+    int tickSpace = AxisPair.getTickSpace(workingSpace); // in plot space
+
+    // where the tick should begin in the working space in pixels
+    int margin = AxisPair.getTickStartOffset(workingSpace, tickSpace); // in plot space BigDecimal gridStep = getGridStepForDecimal(tickSpace);
+
+    BigDecimal gridStep = getGridStep(tickSpace);
+    BigDecimal firstPosition = getFirstPosition(minValue, gridStep);
+
+    // generate all tickLabels and tickLocations from the first to last position
+    for (BigDecimal tickPosition = firstPosition; tickPosition.compareTo(maxValue) <= 0; tickPosition = tickPosition.add(gridStep)) {
 
+      tickLabels.add(formatDateValue(tickPosition, minValue, maxValue));
+      // here we convert tickPosition finally to plot space, i.e. pixels
+      int tickLabelPosition = (int) (margin + ((tickPosition.subtract(minValue)).doubleValue() / (maxValue.subtract(minValue)).doubleValue() * tickSpace));
+      tickLocations.add(tickLabelPosition);
+    }
   }
 
   /**
@@ -61,67 +100,21 @@ public class DateAxisTickCalculator extends AxisTickCalculator {
     // the span of the data
     double span = Math.abs(maxValue.subtract(minValue).doubleValue()); // in data space
 
-    int tickMarkSpaceHint = (axisDirection == Direction.X ? DEFAULT_TICK_MARK_STEP_HINT_X : DEFAULT_TICK_MARK_STEP_HINT_Y);
+    double gridStepHint = span / tickSpace * DEFAULT_TICK_MARK_STEP_HINT_X;
 
-    // for very short plots, squeeze some more ticks in than normal
-    if (axisDirection == Direction.Y && tickSpace < 160) {
-      tickMarkSpaceHint = 25;
-    }
+    BigDecimal gridStep;
+    if (span < SEC_SCALE) {
+      // decimal
+      gridStep = getGridStepDecimal(gridStepHint);
 
-    double gridStepHint = span / tickSpace * tickMarkSpaceHint;
-
-    // gridStepHint --> significand * 10 ** exponent
-    // e.g. 724.1 --> 7.241 * 10 ** 2
-    double significand = gridStepHint;
-    int exponent = 0;
-    if (significand == 0) {
-      exponent = 1;
-    } else if (significand < 1) {
-      while (significand < 1) {
-        significand *= 10.0;
-        exponent--;
-      }
     } else {
-      while (significand >= 10) {
-        significand /= 10.0;
-        exponent++;
-      }
-    }
 
-    // calculate the grid step with hint.
-    BigDecimal gridStep;
-    if (significand > 7.5) {
-      // gridStep = 10.0 * 10 ** exponent
-      gridStep = BigDecimal.TEN.multiply(pow(10, exponent));
-    } else if (significand > 3.5) {
-      // gridStep = 5.0 * 10 ** exponent
-      gridStep = new BigDecimal(new Double(5).toString()).multiply(pow(10, exponent));
-    } else if (significand > 1.5) {
-      // gridStep = 2.0 * 10 ** exponent
-      gridStep = new BigDecimal(new Double(2).toString()).multiply(pow(10, exponent));
-    } else {
-      // gridStep = 1.0 * 10 ** exponent
-      gridStep = pow(10, exponent);
-    }
-    return gridStep;
-  }
+      // date
+      gridStep = getGridStepDecimal(gridStepHint);
 
-  /**
-   * Calculates the value of the first argument raised to the power of the second argument.
-   * 
-   * @param base the base
-   * @param exponent the exponent
-   * @return the value <tt>a<sup>b</sup></tt> in <tt>BigDecimal</tt>
-   */
-  private BigDecimal pow(double base, int exponent) {
-
-    BigDecimal value;
-    if (exponent > 0) {
-      value = new BigDecimal(new Double(base).toString()).pow(exponent);
-    } else {
-      value = BigDecimal.ONE.divide(new BigDecimal(new Double(base).toString()).pow(-exponent));
     }
-    return value;
+
+    return gridStep;
   }
 
   @Override
@@ -136,6 +129,47 @@ public class DateAxisTickCalculator extends AxisTickCalculator {
     return firstPosition;
   }
 
+  /**
+   * Format a date value
+   * 
+   * @param value
+   * @param min
+   * @param max
+   * @return
+   */
+  public String formatDateValue(BigDecimal value, BigDecimal min, BigDecimal max) {
+
+    String datePattern;
+    // TODO check if min and max are the same, then calculate this differently
+
+    // intelligently set date pattern if none is given
+    long diff = max.subtract(min).longValue();
+
+    if (diff < SEC_SCALE) {
+      datePattern = "ss:S";
+    } else if (diff < MIN_SCALE) {
+      datePattern = "mm:ss";
+    } else if (diff < HOUR_SCALE) {
+      datePattern = "HH:mm";
+    } else if (diff < DAY_SCALE) {
+      datePattern = "EEE HH:mm";
+    } else if (diff < WEEK_SCALE) {
+      datePattern = "EEE";
+    } else if (diff < MONTH_SCALE) {
+      datePattern = "MMM-dd";
+    } else if (diff < YEAR_SCALE) {
+      datePattern = "yyyy:MMM";
+    } else {
+      datePattern = "yyyy";
+    }
+
+    SimpleDateFormat simpleDateformat = new SimpleDateFormat(datePattern, styleManager.getLocale());
+    simpleDateformat.setTimeZone(styleManager.getTimezone());
+    simpleDateformat.applyPattern(datePattern);
+
+    return simpleDateformat.format(value.longValueExact());
+  }
+
   @Override
   public AxisType getAxisType() {
 
diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateFormatter.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateFormatter.java
deleted file mode 100644
index 448002e0..00000000
--- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateFormatter.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/**
- * Copyright (C) 2013 Xeiam LLC http://xeiam.com
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- * 
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-package com.xeiam.xchart.internal.chartpart.axistickcalculator;
-
-import java.math.BigDecimal;
-import java.text.SimpleDateFormat;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-
-/**
- * @author timmolter
- */
-public class DateFormatter {
-
-  private String datePattern;
-  private Locale locale;
-  private TimeZone timezone;
-
-  private static final long SEC_SCALE = TimeUnit.SECONDS.toMillis(1L);
-  private static final long MIN_SCALE = TimeUnit.MINUTES.toMillis(1L);
-  private static final long HOUR_SCALE = TimeUnit.HOURS.toMillis(1L);
-  private static final long DAY_SCALE = TimeUnit.DAYS.toMillis(1L);
-  private static final long WEEK_SCALE = TimeUnit.DAYS.toMillis(1L) * 7;
-  private static final long MONTH_SCALE = TimeUnit.DAYS.toMillis(1L) * 31;
-  private static final long YEAR_SCALE = TimeUnit.DAYS.toMillis(1L) * 365;
-
-  /**
-   * Constructor
-   */
-  public DateFormatter() {
-
-    datePattern = "HHmmss";
-    locale = Locale.getDefault();
-    timezone = TimeZone.getDefault();
-  }
-
-  /**
-   * Format a date value
-   * 
-   * @param value
-   * @param min
-   * @param max
-   * @return
-   */
-  public String formatDateValue(BigDecimal value, BigDecimal min, BigDecimal max) {
-
-    // TODO check if min and max are the same, then calculate this differently
-
-    // intelligently set date pattern if none is given
-    long diff = max.subtract(min).longValue();
-
-    if (diff < SEC_SCALE) {
-      datePattern = "ss:S";
-    } else if (diff < MIN_SCALE) {
-      datePattern = "mm:ss";
-    } else if (diff < HOUR_SCALE) {
-      datePattern = "HH:mm";
-    } else if (diff < DAY_SCALE) {
-      datePattern = "EEE HH:mm";
-    } else if (diff < WEEK_SCALE) {
-      datePattern = "EEE";
-    } else if (diff < MONTH_SCALE) {
-      datePattern = "MMM-dd";
-    } else if (diff < YEAR_SCALE) {
-      datePattern = "yyyy:MMM";
-    } else {
-      datePattern = "yyyy";
-    }
-
-    SimpleDateFormat simpleDateformat = new SimpleDateFormat(datePattern, locale);
-    simpleDateformat.setTimeZone(timezone);
-    simpleDateformat.applyPattern(datePattern);
-    return simpleDateformat.format(value.longValueExact());
-
-  }
-
-  /**
-   * Set the String formatter for Data x-axis
-   * 
-   * @param pattern - the pattern describing the date and time format
-   */
-  public void setDatePattern(String datePattern) {
-
-    this.datePattern = datePattern;
-  }
-
-  /**
-   * Set the locale to use for rendering the chart
-   * 
-   * @param locale - the locale to use when formatting Strings and dates for the axis tick labels
-   */
-  public void setLocale(Locale locale) {
-
-    this.locale = locale;
-  }
-
-  /**
-   * Set the timezone to use for formatting Date axis tick labels
-   * 
-   * @param timezone the timezone to use when formatting date data
-   */
-  public void setTimezone(TimeZone timezone) {
-
-    this.timezone = timezone;
-  }
-
-}
diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/NumberAxisTickCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/NumberAxisTickCalculator.java
index 936eba15..7f286d4d 100644
--- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/NumberAxisTickCalculator.java
+++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/NumberAxisTickCalculator.java
@@ -25,6 +25,7 @@ import java.math.BigDecimal;
 
 import com.xeiam.xchart.internal.chartpart.Axis.AxisType;
 import com.xeiam.xchart.internal.chartpart.Axis.Direction;
+import com.xeiam.xchart.internal.chartpart.AxisPair;
 import com.xeiam.xchart.style.StyleManager;
 
 /**
@@ -46,6 +47,35 @@ public class NumberAxisTickCalculator extends AxisTickCalculator {
   public NumberAxisTickCalculator(Direction axisDirection, int workingSpace, BigDecimal minValue, BigDecimal maxValue, StyleManager styleManager) {
 
     super(axisDirection, workingSpace, minValue, maxValue, styleManager);
+    calculate();
+  }
+
+  private void calculate() {
+
+    // a check if all axis data are the exact same values
+    if (minValue == maxValue) {
+      tickLabels.add(formatNumber(maxValue));
+      tickLocations.add((int) (workingSpace / 2.0));
+      return;
+    }
+
+    // tick space - a percentage of the working space available for ticks, i.e. 95%
+    int tickSpace = AxisPair.getTickSpace(workingSpace); // in plot space
+
+    // where the tick should begin in the working space in pixels
+    int margin = AxisPair.getTickStartOffset(workingSpace, tickSpace); // in plot space BigDecimal gridStep = getGridStepForDecimal(tickSpace);
+
+    BigDecimal gridStep = getGridStep(tickSpace);
+    BigDecimal firstPosition = getFirstPosition(minValue, gridStep);
+
+    // generate all tickLabels and tickLocations from the first to last position
+    for (BigDecimal tickPosition = firstPosition; tickPosition.compareTo(maxValue) <= 0; tickPosition = tickPosition.add(gridStep)) {
+
+      tickLabels.add(formatNumber(tickPosition));
+      // here we convert tickPosition finally to plot space, i.e. pixels
+      int tickLabelPosition = (int) (margin + ((tickPosition.subtract(minValue)).doubleValue() / (maxValue.subtract(minValue)).doubleValue() * tickSpace));
+      tickLocations.add(tickLabelPosition);
+    }
   }
 
   /**
diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/NumberFormatter.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/NumberFormatter.java
deleted file mode 100644
index 594fa777..00000000
--- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/NumberFormatter.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/**
- * Copyright (C) 2013 Xeiam LLC http://xeiam.com
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- * 
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-package com.xeiam.xchart.internal.chartpart.axistickcalculator;
-
-import java.math.BigDecimal;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-import java.util.Locale;
-
-/**
- * @author timmolter
- */
-public class NumberFormatter {
-
-  private String normalDecimalPattern;
-  private String scientificDecimalPattern;
-  // TODO move to parent class??
-  private Locale locale;
-
-  /**
-   * Constructor
-   */
-  public NumberFormatter() {
-
-    normalDecimalPattern = "#.####";
-    scientificDecimalPattern = "0.##E0";
-    locale = Locale.getDefault();
-
-  }
-
-  /**
-   * Format a number value, if the override patterns are null, it uses defaults
-   * 
-   * @param value
-   * @return
-   */
-  public String formatNumber(BigDecimal value) {
-
-    NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
-
-    BigDecimal absoluteValue = value.abs();
-
-    if (absoluteValue.compareTo(new BigDecimal("10000.000001")) == -1 && absoluteValue.compareTo(new BigDecimal(".0009999999")) == 1 || BigDecimal.ZERO.compareTo(value) == 0) {
-
-      DecimalFormat normalFormat = (DecimalFormat) numberFormat;
-      normalFormat.applyPattern(normalDecimalPattern);
-      return normalFormat.format(value);
-
-    } else {
-
-      DecimalFormat scientificFormat = (DecimalFormat) numberFormat;
-      scientificFormat.applyPattern(scientificDecimalPattern);
-      return scientificFormat.format(value);
-
-    }
-
-  }
-
-  /**
-   * Set the decimal formatter for all tick labels
-   * 
-   * @param pattern - the pattern describing the decimal format
-   */
-  public void setNormalDecimalPattern(String normalDecimalPattern) {
-
-    this.normalDecimalPattern = normalDecimalPattern;
-  }
-
-  /**
-   * Set the scientific notation formatter for all tick labels
-   * 
-   * @param pattern - the pattern describing the scientific notation format
-   */
-  public void setScientificDecimalPattern(String scientificDecimalPattern) {
-
-    this.scientificDecimalPattern = scientificDecimalPattern;
-  }
-
-  /**
-   * Set the locale to use for rendering the chart
-   * 
-   * @param locale - the locale to use when formatting Strings and dates for the axis tick labels
-   */
-  public void setLocale(Locale locale) {
-
-    this.locale = locale;
-  }
-
-}
diff --git a/xchart/src/main/java/com/xeiam/xchart/style/StyleManager.java b/xchart/src/main/java/com/xeiam/xchart/style/StyleManager.java
index 244f8344..00507e5f 100644
--- a/xchart/src/main/java/com/xeiam/xchart/style/StyleManager.java
+++ b/xchart/src/main/java/com/xeiam/xchart/style/StyleManager.java
@@ -24,9 +24,9 @@ package com.xeiam.xchart.style;
 import java.awt.Color;
 import java.awt.Font;
 import java.awt.Stroke;
+import java.util.Locale;
+import java.util.TimeZone;
 
-import com.xeiam.xchart.internal.chartpart.axistickcalculator.DateFormatter;
-import com.xeiam.xchart.internal.chartpart.axistickcalculator.NumberFormatter;
 import com.xeiam.xchart.style.theme.Theme;
 import com.xeiam.xchart.style.theme.XChartTheme;
 
@@ -97,9 +97,11 @@ public class StyleManager {
   private Color errorBarsColor;
 
   // Formatting ////////////////////////////////
-
-  private NumberFormatter decimalFormatter;
-  private DateFormatter dateFormatter;
+  private String datePattern;
+  private Locale locale;
+  private TimeZone timezone;
+  private String normalDecimalPattern;
+  private String scientificDecimalPattern;
 
   /**
    * Constructor
@@ -160,8 +162,11 @@ public class StyleManager {
     errorBarsColor = theme.getErrorBarsColor();
 
     // Formatting ////////////////////////////////
-    decimalFormatter = new NumberFormatter();
-    dateFormatter = new DateFormatter();
+    datePattern = "HHmmss";
+    locale = Locale.getDefault();
+    timezone = TimeZone.getDefault();
+    normalDecimalPattern = "#.####";
+    scientificDecimalPattern = "0.##E0";
   }
 
   /**
@@ -755,14 +760,78 @@ public class StyleManager {
 
   // Formatting ////////////////////////////////
 
-  public NumberFormatter getDecimalFormatter() {
+  /**
+   * Set the String formatter for Data x-axis
+   * 
+   * @param pattern - the pattern describing the date and time format
+   */
+  public void setDatePattern(String datePattern) {
+
+    this.datePattern = datePattern;
+  }
+
+  public String getDatePattern() {
+
+    return datePattern;
+  }
+
+  /**
+   * Set the locale to use for rendering the chart
+   * 
+   * @param locale - the locale to use when formatting Strings and dates for the axis tick labels
+   */
+  public void setLocale(Locale locale) {
+
+    this.locale = locale;
+  }
 
-    return decimalFormatter;
+  public Locale getLocale() {
+
+    return locale;
   }
 
-  public DateFormatter getDateFormatter() {
+  /**
+   * Set the timezone to use for formatting Date axis tick labels
+   * 
+   * @param timezone the timezone to use when formatting date data
+   */
+  public void setTimezone(TimeZone timezone) {
 
-    return dateFormatter;
+    this.timezone = timezone;
   }
 
+  public TimeZone getTimezone() {
+
+    return timezone;
+  }
+
+  /**
+   * Set the decimal formatter for all tick labels
+   * 
+   * @param pattern - the pattern describing the decimal format
+   */
+  public void setNormalDecimalPattern(String normalDecimalPattern) {
+
+    this.normalDecimalPattern = normalDecimalPattern;
+  }
+
+  public String getNormalDecimalPattern() {
+
+    return normalDecimalPattern;
+  }
+
+  /**
+   * Set the scientific notation formatter for all tick labels
+   * 
+   * @param pattern - the pattern describing the scientific notation format
+   */
+  public void setScientificDecimalPattern(String scientificDecimalPattern) {
+
+    this.scientificDecimalPattern = scientificDecimalPattern;
+  }
+
+  public String getScientificDecimalPattern() {
+
+    return scientificDecimalPattern;
+  }
 }
diff --git a/xchart/src/test/java/com/xeiam/xchart/unit/ValueFormatterTest.java b/xchart/src/test/java/com/xeiam/xchart/unit/ValueFormatterTest.java
index ac59498f..5a27d906 100644
--- a/xchart/src/test/java/com/xeiam/xchart/unit/ValueFormatterTest.java
+++ b/xchart/src/test/java/com/xeiam/xchart/unit/ValueFormatterTest.java
@@ -30,8 +30,9 @@ import java.util.TimeZone;
 
 import org.junit.Test;
 
-import com.xeiam.xchart.internal.chartpart.axistickcalculator.DateFormatter;
-import com.xeiam.xchart.internal.chartpart.axistickcalculator.NumberFormatter;
+import com.xeiam.xchart.internal.chartpart.Axis.Direction;
+import com.xeiam.xchart.internal.chartpart.axistickcalculator.DateAxisTickCalculator;
+import com.xeiam.xchart.style.StyleManager;
 
 /**
  * @author timmolter
@@ -43,59 +44,60 @@ public class ValueFormatterTest {
   @Test
   public void testNumberFormatting() {
 
-    NumberFormatter axisTickLabelFormatter = new NumberFormatter();
+    StyleManager styleManager = new StyleManager();
+    DateAxisTickCalculator dateAxisTickCalculator = new DateAxisTickCalculator(Direction.X, 1000, new BigDecimal(10), new BigDecimal(90), styleManager);
 
     // big
-    axisTickLabelFormatter.setLocale(locale);
+    styleManager.setLocale(locale);
 
     BigDecimal value = new BigDecimal("1");
-    String stringValue = axisTickLabelFormatter.formatNumber(value);
+    String stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("1"));
 
     value = new BigDecimal(1000L);
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("1000"));
 
     value = new BigDecimal("9999");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("9999"));
 
     value = new BigDecimal(20000L);
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("2E4"));
 
     value = new BigDecimal("200.23");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("200.23"));
 
     // small
 
     value = new BigDecimal("0.01");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("0.01"));
 
     value = new BigDecimal("0.001");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("0.001"));
 
     value = new BigDecimal("0.0012");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("0.0012"));
 
     value = new BigDecimal("0.0001");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("1E-4"));
 
     value = new BigDecimal(".00012");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("1.2E-4"));
 
     value = new BigDecimal("0.0");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("0"));
 
     value = new BigDecimal("0");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("0"));
 
     // other case
@@ -111,24 +113,24 @@ public class ValueFormatterTest {
     // assertThat(stringValue, equalTo("0.01"));
 
     // non-default
-    axisTickLabelFormatter.setLocale(Locale.GERMANY);
+    styleManager.setLocale(Locale.GERMANY);
 
     value = new BigDecimal("0.01");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("0,01"));
 
     value = new BigDecimal("200.23");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("200,23"));
 
-    axisTickLabelFormatter.setNormalDecimalPattern("#.#");
+    styleManager.setNormalDecimalPattern("#.#");
     value = new BigDecimal("200.23");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("200,2"));
 
-    axisTickLabelFormatter.setScientificDecimalPattern("0.#E0");
+    styleManager.setScientificDecimalPattern("0.#E0");
     value = new BigDecimal("2009764.23");
-    stringValue = axisTickLabelFormatter.formatNumber(value);
+    stringValue = dateAxisTickCalculator.formatNumber(value);
     assertThat(stringValue, equalTo("2E6"));
 
   }
@@ -136,67 +138,68 @@ public class ValueFormatterTest {
   @Test
   public void testDateFormatting() {
 
-    DateFormatter axisTickLabelFormatter = new DateFormatter();
+    StyleManager styleManager = new StyleManager();
+    DateAxisTickCalculator dateAxisTickCalculator = new DateAxisTickCalculator(Direction.X, 1000, new BigDecimal(10), new BigDecimal(90), styleManager);
 
     TimeZone timeZone = TimeZone.getTimeZone("UTC");
 
-    axisTickLabelFormatter.setLocale(locale);
-    axisTickLabelFormatter.setTimezone(timeZone);
+    styleManager.setLocale(locale);
+    styleManager.setTimezone(timeZone);
 
     // ms
     BigDecimal value = new BigDecimal(1358108105531L);
     BigDecimal min = new BigDecimal(1358108105100L);
     BigDecimal max = new BigDecimal(1358108105900L);
-    String stringValue = axisTickLabelFormatter.formatDateValue(value, min, max);
+    String stringValue = dateAxisTickCalculator.formatDateValue(value, min, max);
     assertThat(stringValue, equalTo("05:531"));
 
     // sec
     value = new BigDecimal(1358108105000L);
     min = new BigDecimal(1358108101000L);
     max = new BigDecimal(1358108109000L);
-    stringValue = axisTickLabelFormatter.formatDateValue(value, min, max);
+    stringValue = dateAxisTickCalculator.formatDateValue(value, min, max);
     assertThat(stringValue, equalTo("15:05"));
 
     // min
     value = new BigDecimal(1358111750000L);
     min = new BigDecimal(1358111690000L);
     max = new BigDecimal(1358111870000L);
-    stringValue = axisTickLabelFormatter.formatDateValue(value, min, max);
+    stringValue = dateAxisTickCalculator.formatDateValue(value, min, max);
     assertThat(stringValue, equalTo("21:15"));
 
     // hour
     value = new BigDecimal(1358111870000L);
     min = new BigDecimal(1358101070000L);
     max = new BigDecimal(1358115470000L);
-    stringValue = axisTickLabelFormatter.formatDateValue(value, min, max);
+    stringValue = dateAxisTickCalculator.formatDateValue(value, min, max);
     assertThat(stringValue, equalTo("Sun 21:17"));
 
     // day
     value = new BigDecimal(1358112317000L);
     min = new BigDecimal(1357939517000L);
     max = new BigDecimal(1358285117000L);
-    stringValue = axisTickLabelFormatter.formatDateValue(value, min, max);
+    stringValue = dateAxisTickCalculator.formatDateValue(value, min, max);
     assertThat(stringValue, equalTo("Sun"));
 
     // week
     value = new BigDecimal(1358112317000L);
     min = new BigDecimal(1357075517000L);
     max = new BigDecimal(1359149117000L);
-    stringValue = axisTickLabelFormatter.formatDateValue(value, min, max);
+    stringValue = dateAxisTickCalculator.formatDateValue(value, min, max);
     assertThat(stringValue, equalTo("Jan-13"));
 
     // month
     value = new BigDecimal(1358112838000L);
     min = new BigDecimal(1354397638000L);
     max = new BigDecimal(1361223238000L);
-    stringValue = axisTickLabelFormatter.formatDateValue(value, min, max);
+    stringValue = dateAxisTickCalculator.formatDateValue(value, min, max);
     assertThat(stringValue, equalTo("2013:Jan"));
 
     // year
     value = new BigDecimal(1358113402000L);
     min = new BigDecimal(1263419002000L);
     max = new BigDecimal(1421185402000L);
-    stringValue = axisTickLabelFormatter.formatDateValue(value, min, max);
+    stringValue = dateAxisTickCalculator.formatDateValue(value, min, max);
     assertThat(stringValue, equalTo("2013"));
 
   }
-- 
GitLab