diff --git a/pom.xml b/pom.xml index 00137946debf16ce18039a09b575dba866058c72..873a8d6cff6fd1aadcc485c3ebbb1acf1b8b02cb 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ <name>Tim Molter</name> </developer> </developers> - + <licenses> <license> <name>The Apache Software License, Version 2.0</name> @@ -31,18 +31,18 @@ <comments>A business-friendly OSS license</comments> </license> </licenses> - + <issueManagement> <system>GitHub</system> <url>https://github.com/timmolter/XChart/issues</url> </issueManagement> - + <scm> <connection>scm:git:git@github.com:timmolter/XChart.git</connection> <developerConnection>scm:git:git@github.com:timmolter/XChart.git</developerConnection> <url>git@github.com:timmolter/XChart.git</url> </scm> - + <ciManagement> <url>http://ci.xeiam.com/</url> </ciManagement> @@ -52,6 +52,29 @@ <module>xchart-examples</module> </modules> + <dependencies> + <!-- JUnit --> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-library</artifactId> + <version>1.3</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <!-- dep leaves out hamcrest classes --> + <artifactId>junit-dep</artifactId> + <version>4.10</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-core</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + <build> <plugins> <!-- Ensure compilation is done under Java 6 in all environments --> diff --git a/xchart-examples/src/main/java/com/xeiam/xchart/demo/XChartDemo.java b/xchart-examples/src/main/java/com/xeiam/xchart/demo/XChartDemo.java index 460caf17a67d6af9d8ad29a5e88decd3d6d047f5..7c224eb693504bacc5a1eca9db9e3ea3dce46605 100644 --- a/xchart-examples/src/main/java/com/xeiam/xchart/demo/XChartDemo.java +++ b/xchart-examples/src/main/java/com/xeiam/xchart/demo/XChartDemo.java @@ -32,6 +32,7 @@ import com.xeiam.xchart.XChartPanel; import com.xeiam.xchart.demo.charts.Example10; import com.xeiam.xchart.demo.charts.Example2; import com.xeiam.xchart.demo.charts.Example3; +import com.xeiam.xchart.demo.charts.Example4; import com.xeiam.xchart.demo.charts.Example5; import com.xeiam.xchart.demo.charts.Example6; import com.xeiam.xchart.demo.charts.Example7; @@ -130,6 +131,9 @@ public class XChartDemo extends JPanel implements TreeSelectionListener { chart = new DefaultMutableTreeNode(new ChartInfo("Example3 - Multiple curves on one Chart", new Example3().getChart())); category.add(chart); + chart = new DefaultMutableTreeNode(new ChartInfo("Example4 - Date Axis", new Example4().getChart())); + category.add(chart); + chart = new DefaultMutableTreeNode(new ChartInfo("Example5 - Vertical and horizontal lines", new Example5().getChart())); category.add(chart); diff --git a/xchart-examples/src/main/java/com/xeiam/xchart/demo/charts/Example4.java b/xchart-examples/src/main/java/com/xeiam/xchart/demo/charts/Example4.java new file mode 100644 index 0000000000000000000000000000000000000000..7c03d3526c88ddb7f530b2e3d6c50cb7b38e2a9d --- /dev/null +++ b/xchart-examples/src/main/java/com/xeiam/xchart/demo/charts/Example4.java @@ -0,0 +1,76 @@ +/** + * Copyright 2011-2013 Xeiam LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.xeiam.xchart.demo.charts; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import com.xeiam.xchart.Chart; +import com.xeiam.xchart.Series; +import com.xeiam.xchart.SwingWrapper; + +/** + * Date Axis + * + * @author timmolter + */ +public class Example4 implements ExampleChart { + + public static void main(String[] args) { + + ExampleChart exampleChart = new Example4(); + Chart chart = exampleChart.getChart(); + new SwingWrapper(chart).displayChart(); + } + + @Override + public Chart getChart() { + + // Create Chart + Chart chart = new Chart(800, 600); + + // generates linear data + Collection<Date> xData = new ArrayList<Date>(); + Collection<Number> yData = new ArrayList<Number>(); + + DateFormat sdf = new SimpleDateFormat("yyyy.MM.dd.HH"); + Date date = null; + for (int i = 1; i <= 10; i++) { + + try { + date = sdf.parse("2012.12.22." + (10 + i)); + } catch (ParseException e) { + e.printStackTrace(); + } + xData.add(date); + yData.add(Math.random() * i); + } + + // Customize Chart + chart.setTitle("Example4"); + chart.setXAxisTitle("time of day"); + chart.setYAxisTitle("gigawatts"); + + Series series = chart.addDateSeries("value", xData, yData); + + return chart; + } + +} diff --git a/xchart/src/main/java/com/xeiam/xchart/SeriesColor.java b/xchart/src/main/java/com/xeiam/xchart/SeriesColor.java index c3a8fbf8978d29eb9f11ee29c0f67b80b2b04a39..8640a462cc4e4726c1f502fbf561305903b433ad 100644 --- a/xchart/src/main/java/com/xeiam/xchart/SeriesColor.java +++ b/xchart/src/main/java/com/xeiam/xchart/SeriesColor.java @@ -55,7 +55,7 @@ public enum SeriesColor { CYAN(9, new Color(0, 255, 255)), /** BROWN */ - BROWN(10, new Color(150, 74, 0)), + BROWN(10, new Color(102, 56, 10)), /** BLACK */ BLACK(11, new Color(0, 0, 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 f8e7dfb2b3bc17b354b9c289f7fecce3c9817063..dd2f5a2c6aae981f4bd766939e9f28810329a43f 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 @@ -18,9 +18,6 @@ package com.xeiam.xchart.internal.chartpart; import java.awt.Graphics2D; import java.awt.Rectangle; import java.math.BigDecimal; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.text.SimpleDateFormat; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -29,6 +26,7 @@ import com.xeiam.xchart.internal.chartpart.Axis.AxisType; import com.xeiam.xchart.internal.chartpart.Axis.Direction; import com.xeiam.xchart.internal.interfaces.IChartPart; import com.xeiam.xchart.internal.interfaces.IHideable; +import com.xeiam.xchart.internal.misc.AxisValueFormatterUtil; /** * An axis tick. @@ -64,9 +62,9 @@ public class AxisTick implements IChartPart, IHideable { /** the Locale for Date tick labels */ public Locale locale; - public String normalDecimalPattern; - public String scientificDecimalPattern; - public String datePattern; + public String normalDecimalPattern = null; + public String scientificDecimalPattern = null; + public String datePattern = null; /** the bounds */ private Rectangle bounds; @@ -85,12 +83,6 @@ public class AxisTick implements IChartPart, IHideable { axisTickLabels = new AxisTickLabels(this); axisTickMarks = new AxisTickMarks(this); - // formatting - locale = Locale.getDefault(); - normalDecimalPattern = "#.####"; - scientificDecimalPattern = "0.##E0"; - datePattern = "HHmmss"; - } @Override @@ -248,32 +240,21 @@ public class AxisTick implements IChartPart, IHideable { return value; } + /** + * Format the number + * + * @param value The number to be formatted + * @return The formatted number in String form + */ private String format(BigDecimal value) { if (axis.axisType == AxisType.NUMBER) { - NumberFormat nf = NumberFormat.getNumberInstance(locale); - - if (Math.abs(value.doubleValue()) <= 9999 && Math.abs(value.doubleValue()) > .01 || value.doubleValue() == 0) { - - DecimalFormat normalFormat = (DecimalFormat) nf; - normalFormat.applyPattern(normalDecimalPattern); - return normalFormat.format(value.doubleValue()); + return AxisValueFormatterUtil.formatNumber(value, normalDecimalPattern, scientificDecimalPattern, locale); - } else { - - DecimalFormat scientificFormat = (DecimalFormat) nf; - scientificFormat.applyPattern(scientificDecimalPattern); - return scientificFormat.format(value.doubleValue()); - - } } else { - // TODO set this more intelligently - SimpleDateFormat simpleDateformat = new SimpleDateFormat(datePattern, locale); - simpleDateformat.applyPattern(datePattern); - return simpleDateformat.format(value.longValueExact()); - + return AxisValueFormatterUtil.formatDateValue(value, axis.min, axis.max, datePattern, locale); } } diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/misc/AxisValueFormatterUtil.java b/xchart/src/main/java/com/xeiam/xchart/internal/misc/AxisValueFormatterUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..1a6c184ad4d636a5915bde6a6fb93fb6bf64cbc1 --- /dev/null +++ b/xchart/src/main/java/com/xeiam/xchart/internal/misc/AxisValueFormatterUtil.java @@ -0,0 +1,130 @@ +/** + * 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.misc; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Locale; + +/** + * @author timmolter + */ +public class AxisValueFormatterUtil { + + private static final String NORMAL_DECIMAL_PATTERN = "#.####"; + private static final String SCIENTIFIC_DECIMAL_PATTERN = "0.##E0"; + private static final String DATE_PATTERN = "HHmmss"; + private static final Locale LOCALE = Locale.getDefault(); + + private static final BigDecimal SEC_SCALE = new BigDecimal(1000L); + private static final BigDecimal MIN_SCALE = new BigDecimal(1000L * 60); + private static final BigDecimal HOUR_SCALE = new BigDecimal(1000L * 60 * 60); + private static final BigDecimal DAY_SCALE = new BigDecimal(1000L * 60 * 60 * 24); + private static final BigDecimal WEEK_SCALE = new BigDecimal(1000L * 60 * 60 * 24 * 7); + private static final BigDecimal MONTH_SCALE = new BigDecimal(1000L * 60 * 60 * 24 * 31); + private static final BigDecimal YEAR_SCALE = new BigDecimal(1000L * 60 * 60 * 24 * 365); + + /** + * Constructor + */ + private AxisValueFormatterUtil() { + + } + + /** + * Format a number value, if the override patterns are null, it uses defaults + * + * @param value + * @param normalDecimalPatternOverride + * @param scientificDecimalPatternOverride + * @param localeOverride + * @return the formatted number as a String + */ + public static String formatNumber(BigDecimal value, String normalDecimalPatternOverride, String scientificDecimalPatternOverride, Locale localeOverride) { + + NumberFormat numberFormat = NumberFormat.getNumberInstance(localeOverride == null ? LOCALE : localeOverride); + + BigDecimal absoluteValue = value.abs(); + + if (absoluteValue.compareTo(new BigDecimal("10000")) == -1 && absoluteValue.compareTo(new BigDecimal(".0001")) == 1 || absoluteValue.equals(new BigDecimal("0.0"))) { + + DecimalFormat normalFormat = (DecimalFormat) numberFormat; + normalFormat.applyPattern(normalDecimalPatternOverride == null ? NORMAL_DECIMAL_PATTERN : normalDecimalPatternOverride); + return normalFormat.format(value); + + } else { + + DecimalFormat scientificFormat = (DecimalFormat) numberFormat; + scientificFormat.applyPattern(scientificDecimalPatternOverride == null ? SCIENTIFIC_DECIMAL_PATTERN : scientificDecimalPatternOverride); + return scientificFormat.format(value); + + } + + } + + /** + * Format a date value + * + * @param value + * @param min + * @param max + * @param datePatternOverride + * @param localeOverride + * @return the formatted date value as a String + */ + public static String formatDateValue(BigDecimal value, BigDecimal min, BigDecimal max, String datePatternOverride, Locale localeOverride) { + + // intelligently set datepattern if none is given + String datePattern = datePatternOverride; + if (datePatternOverride == null) { + datePattern = DATE_PATTERN; + BigDecimal diff = max.subtract(min); + + if (diff.compareTo(SEC_SCALE) == -1) { + datePattern = "ss:S"; + } else if (diff.compareTo(MIN_SCALE) == -1) { + datePattern = "mm:ss"; + } else if (diff.compareTo(HOUR_SCALE) == -1) { + datePattern = "HH:mm"; + } else if (diff.compareTo(DAY_SCALE) == -1) { + datePattern = "dd:HH"; + } else if (diff.compareTo(WEEK_SCALE) == -1) { + datePattern = "EEE"; + } else if (diff.compareTo(MONTH_SCALE) == -1) { + datePattern = "MMM-dd"; + } else if (diff.compareTo(YEAR_SCALE) == -1) { + datePattern = "yyyy:MMM"; + } else { + datePattern = "yyyy"; + } + + } + + SimpleDateFormat simpleDateformat = new SimpleDateFormat(datePattern, localeOverride == null ? LOCALE : localeOverride); + simpleDateformat.applyPattern(datePattern); + return simpleDateformat.format(value.longValueExact()); + + } + +} diff --git a/xchart/src/test/java/com/xeiam/xchart/ValueFormatTest.java b/xchart/src/test/java/com/xeiam/xchart/ValueFormatTest.java new file mode 100644 index 0000000000000000000000000000000000000000..798488bcb7ddbcb63c22f08ac437b02de829ea73 --- /dev/null +++ b/xchart/src/test/java/com/xeiam/xchart/ValueFormatTest.java @@ -0,0 +1,175 @@ +/** + * 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; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.math.BigDecimal; +import java.util.Locale; + +import org.junit.Test; + +import com.xeiam.xchart.internal.misc.AxisValueFormatterUtil; + +/** + * @author timmolter + */ +public class ValueFormatTest { + + // private final String normalDecimalPattern = "#.####"; + // private final String scientificDecimalPattern = "0.##E0"; + // private final String datePattern = "HHmmss"; + private final Locale locale = Locale.US; + + @Test + public void testNumberFormatting() { + + // big + + BigDecimal value = new BigDecimal("1"); + String stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, locale); + assertThat(stringValue, equalTo("1")); + + value = new BigDecimal("1000"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, locale); + assertThat(stringValue, equalTo("1000")); + + value = new BigDecimal("9999"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, locale); + assertThat(stringValue, equalTo("9999")); + + value = new BigDecimal("20000"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, locale); + assertThat(stringValue, equalTo("2E4")); + + value = new BigDecimal("200.23"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, locale); + assertThat(stringValue, equalTo("200.23")); + + // small + + value = new BigDecimal("0.01"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, locale); + assertThat(stringValue, equalTo("0.01")); + + value = new BigDecimal("0.001"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, locale); + assertThat(stringValue, equalTo("0.001")); + + value = new BigDecimal("0.0001"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, locale); + assertThat(stringValue, equalTo("1E-4")); + + // other case + + // TODO handle these cases better + + // value = new BigDecimal("12228120"); + // stringValue = NumberFormatterUtil.formatNumber(value, null, null, locale); + // assertThat(stringValue, equalTo("0.01")); + + // value = new BigDecimal("0.00000000230000056765"); + // stringValue = NumberFormatterUtil.formatNumber(value, null, null, locale); + // assertThat(stringValue, equalTo("0.01")); + + // non-default + + value = new BigDecimal("0.01"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, Locale.GERMANY); + assertThat(stringValue, equalTo("0,01")); + + value = new BigDecimal("200.23"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, null, Locale.GERMANY); + assertThat(stringValue, equalTo("200,23")); + + value = new BigDecimal("200.23"); + stringValue = AxisValueFormatterUtil.formatNumber(value, "#.#", null, Locale.GERMANY); + assertThat(stringValue, equalTo("200,2")); + + value = new BigDecimal("2009764.23"); + stringValue = AxisValueFormatterUtil.formatNumber(value, null, "0.#E0", Locale.GERMANY); + assertThat(stringValue, equalTo("2E6")); + + } + + @Test + public void testDateFormatting() { + + // ms + BigDecimal value = new BigDecimal("1358108105531"); + BigDecimal min = new BigDecimal("1358108105100"); + BigDecimal max = new BigDecimal("1358108105900"); + String stringValue = AxisValueFormatterUtil.formatDateValue(value, min, max, null, locale); + assertThat(stringValue, equalTo("05:531")); + + // sec + value = new BigDecimal("1358108105000"); + min = new BigDecimal("1358108101000"); + max = new BigDecimal("1358108109000"); + stringValue = AxisValueFormatterUtil.formatDateValue(value, min, max, null, locale); + assertThat(stringValue, equalTo("15:05")); + + // min + value = new BigDecimal("1358111750000"); + min = new BigDecimal("1358111690000"); + max = new BigDecimal("1358111870000"); + stringValue = AxisValueFormatterUtil.formatDateValue(value, min, max, null, locale); + assertThat(stringValue, equalTo("22:15")); + + // hour + value = new BigDecimal("1358111870000"); + min = new BigDecimal("1358101070000"); + max = new BigDecimal("1358115470000"); + stringValue = AxisValueFormatterUtil.formatDateValue(value, min, max, null, locale); + assertThat(stringValue, equalTo("13:22")); + + // day + value = new BigDecimal("1358112317000"); + min = new BigDecimal("1357939517000"); + max = new BigDecimal("1358285117000"); + stringValue = AxisValueFormatterUtil.formatDateValue(value, min, max, null, locale); + assertThat(stringValue, equalTo("Sun")); + + // week + value = new BigDecimal("1358112317000"); + min = new BigDecimal("1357075517000"); + max = new BigDecimal("1359149117000"); + stringValue = AxisValueFormatterUtil.formatDateValue(value, min, max, null, locale); + assertThat(stringValue, equalTo("Jan-13")); + + // month + value = new BigDecimal(1358112838000L); + min = new BigDecimal(1354397638000L); + max = new BigDecimal(1361223238000L); + stringValue = AxisValueFormatterUtil.formatDateValue(value, min, max, null, locale); + assertThat(stringValue, equalTo("2013:Jan")); + + // year + value = new BigDecimal(1358113402000L); + min = new BigDecimal(1263419002000L); + max = new BigDecimal(1421185402000L); + stringValue = AxisValueFormatterUtil.formatDateValue(value, min, max, null, locale); + assertThat(stringValue, equalTo("2013")); + + } +}