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/line/LineChart04.java index 5e15c2e07a46d2075ce7b9c46648c98920acc60a..fa9546424783d3542aeca5633df227da72ff8d29 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/line/LineChart04.java @@ -72,7 +72,7 @@ public class LineChart04 implements ExampleChart { chart.setChartTitle("LineChart04"); chart.setXAxisTitle("time of day"); chart.setYAxisTitle("gigawatts"); - chart.getValueFormatter().setTimezone(TimeZone.getTimeZone("UTC")); + chart.getStyleManager().getDateFormatter().setTimezone(TimeZone.getTimeZone("UTC")); chart.getStyleManager().setLegendPosition(LegendPosition.InsideNW); Series series = chart.addDateSeries("value", xData, yData); 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 d6d73cd61e8a2b67eaf78c783b84446bc60af1b3..39348ac9c3b4ad6e245276743c5348c98ef092b7 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.getValueFormatter().setDatePattern("dd-MMM"); - chart.getValueFormatter().setNormalDecimalPattern("#.000"); - chart.getValueFormatter().setLocale(Locale.GERMAN); + chart.getStyleManager().getDateFormatter().setDatePattern("dd-MMM"); + chart.getStyleManager().getDecimalFormatter().setNormalDecimalPattern("#.000"); + chart.getStyleManager().getDateFormatter().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/Chart.java b/xchart/src/main/java/com/xeiam/xchart/Chart.java index 2fb48add0cd16472fa27874e7de80b09c0aef933..17e51c2934f518b4fa89cb38a6a4ecf7d244eab1 100644 --- a/xchart/src/main/java/com/xeiam/xchart/Chart.java +++ b/xchart/src/main/java/com/xeiam/xchart/Chart.java @@ -27,7 +27,6 @@ import com.xeiam.xchart.internal.chartpart.Legend; import com.xeiam.xchart.internal.chartpart.Plot; import com.xeiam.xchart.style.Series; import com.xeiam.xchart.style.StyleManager; -import com.xeiam.xchart.style.ValueFormatter; import com.xeiam.xchart.style.theme.Theme; /** @@ -41,7 +40,6 @@ public class Chart { private int height; private StyleManager styleManager = new StyleManager(); - private ValueFormatter valueFormatter = new ValueFormatter(); // Chart Parts private Legend chartLegend = new Legend(this); @@ -275,16 +273,6 @@ public class Chart { } - /** - * Gets the Chart's value formatter, which can be used to customize the formatting of numbers and dates - * - * @return - */ - public ValueFormatter getValueFormatter() { - - return valueFormatter; - } - /** * Set the min and max value of the X axis * diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisPair.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisPair.java index d6cc1a8cec2dc8184a694b9cc6032189914dd5db..668b6559542d05955f1c228bc733df1df472bace 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisPair.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisPair.java @@ -164,7 +164,7 @@ public class AxisPair implements ChartPart { * @param workingSpace * @return */ - protected static int getTickSpace(int workingSpace) { + public static int getTickSpace(int workingSpace) { return (int) (workingSpace * 0.95); } @@ -176,7 +176,7 @@ public class AxisPair implements ChartPart { * @param tickSpace * @return */ - protected static int getTickStartOffset(int workingSpace, int tickSpace) { + public static int getTickStartOffset(int workingSpace, int tickSpace) { int marginSpace = workingSpace - tickSpace; return (int) (marginSpace / 2.0); 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 1f1b99c8090f5923afd0304a077e868c53f27e89..8e83c3c5b2409d4d7936c024012a172301580b39 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 @@ -20,6 +20,10 @@ import java.awt.Rectangle; 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; /** * An axis tick @@ -41,7 +45,7 @@ public class AxisTick implements ChartPart { /** the visibility state of axistick */ private boolean isVisible = true; // default to true - AxisTickComputer axisTickComputer; + GridStep gridStep = null; /** * Constructor @@ -77,7 +81,21 @@ public class AxisTick implements ChartPart { // System.out.println("workingspace= " + workingSpace); } - axisTickComputer = new AxisTickComputer(axis.getDirection(), workingSpace, axis.getMin(), axis.getMax(), getChart().getValueFormatter(), axis.getAxisType()); + // //////////////////////// + + if (axis.getAxisType() == AxisType.Number) { + + gridStep = new DecimalGridStep(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()); + + } else if (axis.getAxisType() == AxisType.Logarithmic) { + + } + + // ///////////////////////// if (isVisible) { @@ -120,11 +138,11 @@ public class AxisTick implements ChartPart { public List<Integer> getTickLocations() { - return axisTickComputer.getTickLocations(); + return gridStep.getTickLocations(); } public List<String> getTickLabels() { - return axisTickComputer.getTickLabels(); + return gridStep.getTickLabels(); } } diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickComputer.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickComputer.java deleted file mode 100644 index 07e8a090ba99f15f48b9108100211bd4c8ef1934..0000000000000000000000000000000000000000 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickComputer.java +++ /dev/null @@ -1,153 +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; - -import java.math.BigDecimal; -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.gridstep.DecimalGridStep; -import com.xeiam.xchart.style.ValueFormatter; - -/** - * This class encapsulates the logic to generate the axis tick mark and axis tick label data for rendering the axis ticks - * - * @author timmolter - */ -public class AxisTickComputer { - - /** 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 ValueFormatter valueFormatter; - - private final AxisType axisType; - - /** - * Constructor - * - * @param axisDirection - * @param workingSpace - * @param minValue - * @param maxValue - * @param valueFormatter - * @param axisType - */ - public AxisTickComputer(Direction axisDirection, int workingSpace, BigDecimal minValue, BigDecimal maxValue, ValueFormatter valueFormatter, AxisType axisType) { - - this.axisDirection = axisDirection; - this.workingSpace = workingSpace; - this.minValue = minValue; - this.maxValue = maxValue; - this.valueFormatter = valueFormatter; - this.axisType = axisType; - - computeAxisTick(); - } - - private void computeAxisTick() { - - System.out.println("workingSpace= " + workingSpace); - - // a check if all axis data are the exact same values - if (maxValue == minValue) { - tickLabels.add(format(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 - - // the span of the data - double span = Math.abs(maxValue.subtract(minValue).doubleValue()); // in data space - - BigDecimal gridStep = null; - BigDecimal firstPosition = null; - if (axisType == AxisType.Number) { - - DecimalGridStep decimalGridStepHelper = new DecimalGridStep(); - gridStep = decimalGridStepHelper.getGridStepForDecimal(axisDirection, span, tickSpace); - firstPosition = decimalGridStepHelper.getFirstPosition(minValue, gridStep); - - } else if (axisType == AxisType.Date) { - - } else if (axisType == AxisType.Logarithmic) { - - } - - // 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(format(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); - } - - } - - /** - * Format the number - * - * @param value The number to be formatted - * @return The formatted number in String form - */ - private String format(BigDecimal value) { - - if (axisType == AxisType.Number) { - - return valueFormatter.formatNumber(value); - } else { - - return valueFormatter.formatDateValue(value, minValue, maxValue); - } - } - - public List<Integer> getTickLocations() { - - return tickLocations; - } - - public List<String> getTickLabels() { - - return tickLabels; - } - -} diff --git a/xchart/src/main/java/com/xeiam/xchart/style/ValueFormatter.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateFormatter.java similarity index 68% rename from xchart/src/main/java/com/xeiam/xchart/style/ValueFormatter.java rename to xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateFormatter.java index 19b15d428754e720e12c4e24dd059c3968ea84fd..21fae0c6bce48bd3545f852930c9868721577c4e 100644 --- a/xchart/src/main/java/com/xeiam/xchart/style/ValueFormatter.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateFormatter.java @@ -19,11 +19,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.xeiam.xchart.style; +package com.xeiam.xchart.internal.chartpart.gridstep; import java.math.BigDecimal; -import java.text.DecimalFormat; -import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Locale; import java.util.TimeZone; @@ -32,10 +30,8 @@ import java.util.concurrent.TimeUnit; /** * @author timmolter */ -public class ValueFormatter { +public class DateFormatter { - private String normalDecimalPattern; - private String scientificDecimalPattern; private String datePattern; private Locale locale; private TimeZone timezone; @@ -51,43 +47,13 @@ public class ValueFormatter { /** * Constructor */ - public ValueFormatter() { + public DateFormatter() { - normalDecimalPattern = "#.####"; - scientificDecimalPattern = "0.##E0"; datePattern = "HHmmss"; locale = Locale.getDefault(); timezone = TimeZone.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); - - } - - } - /** * Format a date value * @@ -98,6 +64,8 @@ public class ValueFormatter { */ 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(); @@ -126,26 +94,6 @@ public class ValueFormatter { } - /** - * 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 String formatter for Data x-axis * 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/gridstep/DateGridStep.java new file mode 100644 index 0000000000000000000000000000000000000000..d27af6ebff922b3d851dbe38399e19ef00853024 --- /dev/null +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DateGridStep.java @@ -0,0 +1,205 @@ +/** + * 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.gridstep; + +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; + +/** + * @author timmolter + */ +public class DateGridStep implements GridStep { + + /** 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 + */ + public DateGridStep(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; + + go(); + } + + private void go() { + + // a check if all axis data are the exact same values + if (minValue == maxValue) { + tickLabels.add(styleManager.getDateFormatter().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 + 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.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); + } + } + + /** + * 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/internal/chartpart/gridstep/DecimalFormatter.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalFormatter.java new file mode 100644 index 0000000000000000000000000000000000000000..abf2d54d1cf25f0051f8605ea7618c8166841493 --- /dev/null +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalFormatter.java @@ -0,0 +1,108 @@ +/** + * 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.gridstep; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * @author timmolter + */ +public class DecimalFormatter { + + private String normalDecimalPattern; + private String scientificDecimalPattern; + // TODO move to parent class?? + private Locale locale; + + /** + * Constructor + */ + public DecimalFormatter() { + + 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/internal/chartpart/gridstep/DecimalGridStep.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalGridStep.java index 6748e34687284a1cf9fbbdc7f466bffc786cd62d..b1426418da6de94d78e43bc732a97b5adcbb2690 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalGridStep.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/DecimalGridStep.java @@ -22,13 +22,19 @@ package com.xeiam.xchart.internal.chartpart.gridstep; 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 decimla axes + * * @author timmolter */ -public class DecimalGridStep { +public class DecimalGridStep implements GridStep { /** the default tick mark step hint for x axis */ private static final int DEFAULT_TICK_MARK_STEP_HINT_X = 74; @@ -36,13 +42,81 @@ public class DecimalGridStep { /** 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 + */ + public DecimalGridStep(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; + + go(); + } + + private void go() { + + // 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 */ - public BigDecimal getGridStepForDecimal(Direction axisDirection, double span, int tickSpace) { + 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); @@ -107,7 +181,7 @@ public class DecimalGridStep { return value; } - public BigDecimal getFirstPosition(final BigDecimal min, BigDecimal gridStep) { + private BigDecimal getFirstPosition(final BigDecimal min, BigDecimal gridStep) { BigDecimal firstPosition; if (min.remainder(gridStep).doubleValue() <= 0.0) { @@ -118,4 +192,16 @@ public class DecimalGridStep { 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/internal/chartpart/gridstep/GridStep.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/GridStep.java new file mode 100644 index 0000000000000000000000000000000000000000..f10ec2bfdf1da6c8a3044a85eb9a5314553d57cd --- /dev/null +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/gridstep/GridStep.java @@ -0,0 +1,35 @@ +/** + * 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.gridstep; + +import java.util.List; + +/** + * @author timmolter + */ +public interface GridStep { + + public List<Integer> getTickLocations(); + + public List<String> getTickLabels(); + +} 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 ce15199f3cd94a4d6b2306c1bd0c56f259e0a093..32b9701b6d7652037cd9513b4d57763002c958d7 100644 --- a/xchart/src/main/java/com/xeiam/xchart/style/StyleManager.java +++ b/xchart/src/main/java/com/xeiam/xchart/style/StyleManager.java @@ -25,6 +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.style.theme.Theme; import com.xeiam.xchart.style.theme.XChartTheme; @@ -94,6 +96,11 @@ public class StyleManager { // Error Bars /////////////////////////////// private Color errorBarsColor; + // Formatting //////////////////////////////// + + private DecimalFormatter decimalFormatter; + private DateFormatter dateFormatter; + /** * Constructor */ @@ -151,6 +158,10 @@ public class StyleManager { // Error Bars /////////////////////////////// errorBarsColor = theme.getErrorBarsColor(); + + // Formatting //////////////////////////////// + decimalFormatter = new DecimalFormatter(); + dateFormatter = new DateFormatter(); } /** @@ -741,4 +752,17 @@ public class StyleManager { return errorBarsColor; } + + // Formatting //////////////////////////////// + + public DecimalFormatter getDecimalFormatter() { + + return decimalFormatter; + } + + public DateFormatter getDateFormatter() { + + return dateFormatter; + } + } diff --git a/xchart/src/test/java/com/xeiam/xchart/unit/DecimalGridStepTest.java b/xchart/src/test/java/com/xeiam/xchart/unit/DecimalGridStepTest.java index 265fbf938c27778cf20a8a586dcc6e17e84c7538..c85b834bb1dcc76c63773c64002be53872b07e92 100644 --- a/xchart/src/test/java/com/xeiam/xchart/unit/DecimalGridStepTest.java +++ b/xchart/src/test/java/com/xeiam/xchart/unit/DecimalGridStepTest.java @@ -21,13 +21,8 @@ */ package com.xeiam.xchart.unit; -import java.math.BigDecimal; - import org.junit.Test; -import com.xeiam.xchart.internal.chartpart.Axis.Direction; -import com.xeiam.xchart.internal.chartpart.gridstep.DecimalGridStep; - /** * @author timmolter */ @@ -36,11 +31,11 @@ public class DecimalGridStepTest { @Test public void testDateOneMinuteTimespan() { - DecimalGridStep decimalGridStep = new DecimalGridStep(); - BigDecimal gridStep = decimalGridStep.getGridStepForDecimal(Direction.X, 30, 600); - System.out.println("gridStep= " + gridStep); - BigDecimal first = decimalGridStep.getFirstPosition(new BigDecimal(-15), gridStep); - System.out.println("first= " + first); + // DecimalGridStep decimalGridStep = new DecimalGridStep(); + // BigDecimal gridStep = decimalGridStep.getGridStepForDecimal(Direction.X, 30, 600); + // System.out.println("gridStep= " + gridStep); + // BigDecimal first = decimalGridStep.getFirstPosition(new BigDecimal(-15), gridStep); + // System.out.println("first= " + first); } 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 3c501749fe4e0d6c2eaa4b05b0ff5230380944b8..4edb9ce5481b646becde8565df6c259149fb501b 100644 --- a/xchart/src/test/java/com/xeiam/xchart/unit/ValueFormatterTest.java +++ b/xchart/src/test/java/com/xeiam/xchart/unit/ValueFormatterTest.java @@ -30,7 +30,8 @@ import java.util.TimeZone; import org.junit.Test; -import com.xeiam.xchart.style.ValueFormatter; +import com.xeiam.xchart.internal.chartpart.gridstep.DateFormatter; +import com.xeiam.xchart.internal.chartpart.gridstep.DecimalFormatter; /** * @author timmolter @@ -42,7 +43,7 @@ public class ValueFormatterTest { @Test public void testNumberFormatting() { - ValueFormatter axisTickLabelFormatter = new ValueFormatter(); + DecimalFormatter axisTickLabelFormatter = new DecimalFormatter(); // big axisTickLabelFormatter.setLocale(locale); @@ -135,7 +136,7 @@ public class ValueFormatterTest { @Test public void testDateFormatting() { - ValueFormatter axisTickLabelFormatter = new ValueFormatter(); + DateFormatter axisTickLabelFormatter = new DateFormatter(); TimeZone timeZone = TimeZone.getTimeZone("UTC"); diff --git a/xchart/src/test/java/com/xeiam/xchart/unit/XAxisTest.java b/xchart/src/test/java/com/xeiam/xchart/unit/XAxisTest.java deleted file mode 100644 index 4084df72324ee49d716fbb66bfe0619f7e3db01b..0000000000000000000000000000000000000000 --- a/xchart/src/test/java/com/xeiam/xchart/unit/XAxisTest.java +++ /dev/null @@ -1,64 +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.unit; - -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.List; - -import org.junit.Test; - -import com.xeiam.xchart.internal.chartpart.Axis.AxisType; -import com.xeiam.xchart.internal.chartpart.Axis.Direction; -import com.xeiam.xchart.internal.chartpart.AxisTickComputer; -import com.xeiam.xchart.style.ValueFormatter; - -/** - * @author timmolter - */ -public class XAxisTest { - - // @Test - public void testNumber() { - - AxisTickComputer axisTickComputer = new AxisTickComputer(Direction.X, 1000, new BigDecimal(0), new BigDecimal(10), new ValueFormatter(), AxisType.Number); - // Labels - List<String> tickLabels = axisTickComputer.getTickLabels(); - System.out.println(Arrays.toString(tickLabels.toArray())); - // Locations - List<Integer> tickLocations = axisTickComputer.getTickLocations(); - System.out.println(Arrays.toString(tickLocations.toArray())); - } - - @Test - public void testDateOneMinuteTimespan() { - - AxisTickComputer axisTickComputer = new AxisTickComputer(Direction.X, 1000, new BigDecimal(1361031254000L), new BigDecimal(1361031314000L), new ValueFormatter(), AxisType.Date); - // Labels - List<String> tickLabels = axisTickComputer.getTickLabels(); - System.out.println(Arrays.toString(tickLabels.toArray())); - // Locations - List<Integer> tickLocations = axisTickComputer.getTickLocations(); - System.out.println(Arrays.toString(tickLocations.toArray())); - } - -}