diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTick.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTick.java index 8e83c3c5b2409d4d7936c024012a172301580b39..2fdc51574562f33094f15581bd60cacd277f0258 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTick.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTick.java @@ -21,9 +21,10 @@ import java.util.List; import com.xeiam.xchart.Chart; import com.xeiam.xchart.internal.chartpart.Axis.AxisType; -import com.xeiam.xchart.internal.chartpart.gridstep.DateGridStep; -import com.xeiam.xchart.internal.chartpart.gridstep.DecimalGridStep; -import com.xeiam.xchart.internal.chartpart.gridstep.GridStep; +import com.xeiam.xchart.internal.chartpart.axistickcalculator.AxisTickCalculator; +import com.xeiam.xchart.internal.chartpart.axistickcalculator.DateAxisTickCalculator; +import com.xeiam.xchart.internal.chartpart.axistickcalculator.DecimalAxisTickCalculator; +import com.xeiam.xchart.internal.chartpart.axistickcalculator.LogarithmicAxisTickCalculator; /** * An axis tick @@ -45,7 +46,7 @@ public class AxisTick implements ChartPart { /** the visibility state of axistick */ private boolean isVisible = true; // default to true - GridStep gridStep = null; + AxisTickCalculator gridStep = null; /** * Constructor @@ -81,22 +82,19 @@ public class AxisTick implements ChartPart { // System.out.println("workingspace= " + workingSpace); } - // //////////////////////// - if (axis.getAxisType() == AxisType.Number) { - gridStep = new DecimalGridStep(axis.getDirection(), workingSpace, axis.getMin(), axis.getMax(), getChart().getStyleManager()); + gridStep = new DecimalAxisTickCalculator(axis.getDirection(), workingSpace, axis.getMin(), axis.getMax(), getChart().getStyleManager()); } else if (axis.getAxisType() == AxisType.Date) { - gridStep = new DateGridStep(axis.getDirection(), workingSpace, axis.getMin(), axis.getMax(), getChart().getStyleManager()); + gridStep = new DateAxisTickCalculator(axis.getDirection(), workingSpace, axis.getMin(), axis.getMax(), getChart().getStyleManager()); } else if (axis.getAxisType() == AxisType.Logarithmic) { + gridStep = new LogarithmicAxisTickCalculator(axis.getDirection(), workingSpace, axis.getMin(), axis.getMax(), getChart().getStyleManager()); } - // ///////////////////////// - if (isVisible) { axisTickLabels.paint(g); diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/GridStep.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/AxisTickCalculator.java similarity index 92% rename from xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/GridStep.java rename to xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/AxisTickCalculator.java index f10ec2bfdf1da6c8a3044a85eb9a5314553d57cd..8a9242302875a88de7c844313432e22b4ebca795 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/GridStep.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/AxisTickCalculator.java @@ -19,14 +19,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.xeiam.xchart.internal.chartpart.gridstep; +package com.xeiam.xchart.internal.chartpart.axistickcalculator; import java.util.List; /** * @author timmolter */ -public interface GridStep { +public interface AxisTickCalculator { public List<Integer> getTickLocations(); diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateGridStep.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateAxisTickCalculator.java similarity index 93% rename from xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateGridStep.java rename to xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateAxisTickCalculator.java index d27af6ebff922b3d851dbe38399e19ef00853024..fd93c28c67dbf48058e0f0a8098522af22ffbe18 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateGridStep.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateAxisTickCalculator.java @@ -19,7 +19,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.xeiam.xchart.internal.chartpart.gridstep; +package com.xeiam.xchart.internal.chartpart.axistickcalculator; import java.math.BigDecimal; import java.util.LinkedList; @@ -30,9 +30,11 @@ import com.xeiam.xchart.internal.chartpart.AxisPair; import com.xeiam.xchart.style.StyleManager; /** + * This class encapsulates the logic to generate the axis tick mark and axis tick label data for rendering the axis ticks for date axes + * * @author timmolter */ -public class DateGridStep implements GridStep { +public class DateAxisTickCalculator implements AxisTickCalculator { /** the default tick mark step hint for x axis */ private static final int DEFAULT_TICK_MARK_STEP_HINT_X = 74; @@ -63,8 +65,9 @@ public class DateGridStep implements GridStep { * @param workingSpace * @param minValue * @param maxValue + * @param styleManager */ - public DateGridStep(Direction axisDirection, int workingSpace, BigDecimal minValue, BigDecimal maxValue, StyleManager styleManager) { + public DateAxisTickCalculator(Direction axisDirection, int workingSpace, BigDecimal minValue, BigDecimal maxValue, StyleManager styleManager) { this.axisDirection = axisDirection; this.workingSpace = workingSpace; @@ -72,10 +75,10 @@ public class DateGridStep implements GridStep { this.maxValue = maxValue; this.styleManager = styleManager; - go(); + calculate(); } - private void go() { + private void calculate() { // a check if all axis data are the exact same values if (minValue == maxValue) { diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateFormatter.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateFormatter.java similarity index 98% rename from xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateFormatter.java rename to xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateFormatter.java index 21fae0c6bce48bd3545f852930c9868721577c4e..448002e01469b8d3b726735710a90f35a09cb78d 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateFormatter.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DateFormatter.java @@ -19,7 +19,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.xeiam.xchart.internal.chartpart.gridstep; +package com.xeiam.xchart.internal.chartpart.axistickcalculator; import java.math.BigDecimal; import java.text.SimpleDateFormat; diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalGridStep.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DecimalAxisTickCalculator.java similarity index 94% rename from xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalGridStep.java rename to xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DecimalAxisTickCalculator.java index b1426418da6de94d78e43bc732a97b5adcbb2690..f22f06edc4a7852920a95118dfaca5bb16c00a4d 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalGridStep.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DecimalAxisTickCalculator.java @@ -19,7 +19,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.xeiam.xchart.internal.chartpart.gridstep; +package com.xeiam.xchart.internal.chartpart.axistickcalculator; import java.math.BigDecimal; import java.util.LinkedList; @@ -30,11 +30,11 @@ import com.xeiam.xchart.internal.chartpart.AxisPair; import com.xeiam.xchart.style.StyleManager; /** - * This class encapsulates the logic to generate the axis tick mark and axis tick label data for rendering the axis ticks for decimla axes + * This class encapsulates the logic to generate the axis tick mark and axis tick label data for rendering the axis ticks for decimal axes * * @author timmolter */ -public class DecimalGridStep implements GridStep { +public class DecimalAxisTickCalculator implements AxisTickCalculator { /** the default tick mark step hint for x axis */ private static final int DEFAULT_TICK_MARK_STEP_HINT_X = 74; @@ -65,8 +65,9 @@ public class DecimalGridStep implements GridStep { * @param workingSpace * @param minValue * @param maxValue + * @param styleManager */ - public DecimalGridStep(Direction axisDirection, int workingSpace, BigDecimal minValue, BigDecimal maxValue, StyleManager styleManager) { + public DecimalAxisTickCalculator(Direction axisDirection, int workingSpace, BigDecimal minValue, BigDecimal maxValue, StyleManager styleManager) { this.axisDirection = axisDirection; this.workingSpace = workingSpace; @@ -74,10 +75,10 @@ public class DecimalGridStep implements GridStep { this.maxValue = maxValue; this.styleManager = styleManager; - go(); + calculate(); } - private void go() { + private void calculate() { // a check if all axis data are the exact same values if (minValue == maxValue) { diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalFormatter.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DecimalFormatter.java similarity index 98% rename from xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalFormatter.java rename to xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DecimalFormatter.java index abf2d54d1cf25f0051f8605ea7618c8166841493..27fb6d48634794c3d3d4533ba9d14c175c6a79fc 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalFormatter.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/DecimalFormatter.java @@ -19,7 +19,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.xeiam.xchart.internal.chartpart.gridstep; +package com.xeiam.xchart.internal.chartpart.axistickcalculator; import java.math.BigDecimal; import java.text.DecimalFormat; diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/LogarithmicAxisTickCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/LogarithmicAxisTickCalculator.java new file mode 100644 index 0000000000000000000000000000000000000000..7cd44ab005b710922e0fdf7687e1b86e554cfe25 --- /dev/null +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/axistickcalculator/LogarithmicAxisTickCalculator.java @@ -0,0 +1,208 @@ +/** + * 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.util.LinkedList; +import java.util.List; + +import com.xeiam.xchart.internal.chartpart.Axis.Direction; +import com.xeiam.xchart.internal.chartpart.AxisPair; +import com.xeiam.xchart.style.StyleManager; + +/** + * This class encapsulates the logic to generate the axis tick mark and axis tick label data for rendering the axis ticks for decimal axes + * + * @author timmolter + */ +public class LogarithmicAxisTickCalculator implements AxisTickCalculator { + + /** the default tick mark step hint for x axis */ + private static final int DEFAULT_TICK_MARK_STEP_HINT_X = 74; + + /** the default tick mark step hint for y axis */ + private static final int DEFAULT_TICK_MARK_STEP_HINT_Y = 44; + + /** the List of tick label position in pixels */ + private List<Integer> tickLocations = new LinkedList<Integer>();; + + /** the List of tick label values */ + private List<String> tickLabels = new LinkedList<String>(); + + private final Direction axisDirection; + + private final int workingSpace; + + private final BigDecimal minValue; + + private final BigDecimal maxValue; + + private final StyleManager styleManager; + + /** + * Constructor + * + * @param axisDirection + * @param workingSpace + * @param minValue + * @param maxValue + * @param styleManager + */ + public LogarithmicAxisTickCalculator(Direction axisDirection, int workingSpace, BigDecimal minValue, BigDecimal maxValue, StyleManager styleManager) { + + this.axisDirection = axisDirection; + this.workingSpace = workingSpace; + this.minValue = minValue; + this.maxValue = maxValue; + this.styleManager = styleManager; + + calculate(); + } + + private void calculate() { + + // a check if all axis data are the exact same values + if (minValue == maxValue) { + tickLabels.add(styleManager.getDecimalFormatter().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 + System.out.println("tickSpace= " + tickSpace); + + // 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 = getGridStepForDecimal(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(styleManager.getDecimalFormatter().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); + } + } + + /** + * Determine the grid step for the data set given the space in pixels allocated for the axis + * + * @param tickSpace in plot space + * @return + */ + private BigDecimal getGridStepForDecimal(int tickSpace) { + + // 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); + + // for very short plots, squeeze some more ticks in than normal + if (axisDirection == Direction.Y && tickSpace < 160) { + tickMarkSpaceHint = 25; + } + + 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; + } + + /** + * 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; + } + + private BigDecimal getFirstPosition(final BigDecimal min, BigDecimal gridStep) { + + BigDecimal firstPosition; + if (min.remainder(gridStep).doubleValue() <= 0.0) { + firstPosition = min.subtract(min.remainder(gridStep)); + } else { + firstPosition = min.subtract(min.remainder(gridStep)).add(gridStep); + } + return firstPosition; + } + + @Override + public List<Integer> getTickLocations() { + + return tickLocations; + } + + @Override + public List<String> getTickLabels() { + + return tickLabels; + } + +} 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 32b9701b6d7652037cd9513b4d57763002c958d7..37ad7c292ce7199109e29dbc863ffcff547d1b41 100644 --- a/xchart/src/main/java/com/xeiam/xchart/style/StyleManager.java +++ b/xchart/src/main/java/com/xeiam/xchart/style/StyleManager.java @@ -25,8 +25,8 @@ import java.awt.Color; import java.awt.Font; import java.awt.Stroke; -import com.xeiam.xchart.internal.chartpart.gridstep.DateFormatter; -import com.xeiam.xchart.internal.chartpart.gridstep.DecimalFormatter; +import com.xeiam.xchart.internal.chartpart.axistickcalculator.DateFormatter; +import com.xeiam.xchart.internal.chartpart.axistickcalculator.DecimalFormatter; import com.xeiam.xchart.style.theme.Theme; import com.xeiam.xchart.style.theme.XChartTheme; 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 4edb9ce5481b646becde8565df6c259149fb501b..61321f15ca8f07b7beeee86adecfcaccb65d9177 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,8 @@ import java.util.TimeZone; import org.junit.Test; -import com.xeiam.xchart.internal.chartpart.gridstep.DateFormatter; -import com.xeiam.xchart.internal.chartpart.gridstep.DecimalFormatter; +import com.xeiam.xchart.internal.chartpart.axistickcalculator.DateFormatter; +import com.xeiam.xchart.internal.chartpart.axistickcalculator.DecimalFormatter; /** * @author timmolter