diff --git a/README.md b/README.md index 9f85625dafd05a7fabe134bcd2e84a6622335716..84bf97ecdbc010994703e640015c6e0e52f93fdf 100644 --- a/README.md +++ b/README.md @@ -102,3 +102,4 @@ Please report any bugs or submit feature requests to [XChart's Github issue trac ## Donations 1PrZHiJorAw7RQrjP9CJgtPuqr6fU65PKt + diff --git a/pom.xml b/pom.xml index 95cb07e31c48cdffc99e343b6e8b98ff1f08fdb7..784fa47b7cc0a78e876deb4e87b0d99b8ec2824c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ <groupId>com.xeiam.xchart</groupId> <artifactId>xchart-parent</artifactId> - <version>2.3.2-SNAPSHOT</version> + <version>2.3.3-SNAPSHOT</version> <packaging>pom</packaging> <name>XChart Parent</name> <description>Basic Charts for Java Applications</description> diff --git a/xchart-demo/pom.xml b/xchart-demo/pom.xml index 9b8ac3889ef458c792a6066d6475a48aaa8f5f03..6b1858cc2676fb02f3b0a6c72a47efc4534b9e57 100644 --- a/xchart-demo/pom.xml +++ b/xchart-demo/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>com.xeiam.xchart</groupId> <artifactId>xchart-parent</artifactId> - <version>2.3.2-SNAPSHOT</version> + <version>2.3.3-SNAPSHOT</version> </parent> <artifactId>xchart-demo</artifactId> @@ -17,7 +17,7 @@ <dependency> <groupId>com.xeiam.xchart</groupId> <artifactId>xchart</artifactId> - <version>2.3.2-SNAPSHOT</version> + <version>2.3.3-SNAPSHOT</version> </dependency> </dependencies> 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 b0044f5dcc8dbeae3173779055c4118a7f34eefe..7232b4b8a264993283957745b64c8d1fc89f4761 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 @@ -38,6 +38,8 @@ import com.xeiam.xchart.demo.charts.bar.BarChart02; import com.xeiam.xchart.demo.charts.bar.BarChart03; import com.xeiam.xchart.demo.charts.bar.BarChart04; import com.xeiam.xchart.demo.charts.bar.BarChart05; +import com.xeiam.xchart.demo.charts.bar.BarChart06; +import com.xeiam.xchart.demo.charts.bar.BarChart07; import com.xeiam.xchart.demo.charts.date.DateChart01; import com.xeiam.xchart.demo.charts.date.DateChart02; import com.xeiam.xchart.demo.charts.date.DateChart03; @@ -78,8 +80,8 @@ public class XChartDemo extends JPanel implements TreeSelectionListener { private XChartPanel chartPanel; /** real-time chart example */ - final RealtimeChart01 realtimeChart01 = new RealtimeChart01();; - final RealtimeChart02 realtimeChart02 = new RealtimeChart02();; + final RealtimeChart01 realtimeChart01 = new RealtimeChart01(); + final RealtimeChart02 realtimeChart02 = new RealtimeChart02(); Timer timer = new Timer(); /** @@ -192,7 +194,7 @@ public class XChartDemo extends JPanel implements TreeSelectionListener { defaultMutableTreeNode = new DefaultMutableTreeNode(new ChartInfo("AreaChart02 - Null Y-Axis Data Points", new AreaChart02().getChart())); category.add(defaultMutableTreeNode); - // First category + // Line category category = new DefaultMutableTreeNode("Line Charts"); top.add(category); @@ -249,6 +251,12 @@ public class XChartDemo extends JPanel implements TreeSelectionListener { defaultMutableTreeNode = new DefaultMutableTreeNode(new ChartInfo("BarChart05 - GGPlot2 Theme", new BarChart05().getChart())); category.add(defaultMutableTreeNode); + defaultMutableTreeNode = new DefaultMutableTreeNode(new ChartInfo("BarChart06 - Histogram Overlapped", new BarChart06().getChart())); + category.add(defaultMutableTreeNode); + + defaultMutableTreeNode = new DefaultMutableTreeNode(new ChartInfo("BarChart07 - Histogram Not Overlapped", new BarChart07().getChart())); + category.add(defaultMutableTreeNode); + // Theme category category = new DefaultMutableTreeNode("Chart Themes"); top.add(category); diff --git a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart04.java b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart04.java index 74400142b1590d8c48cdcc6f3223735c26699d4b..5b51c274c0befe39cee1d9bbeaff7f606fc60cec 100644 --- a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart04.java +++ b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart04.java @@ -47,7 +47,7 @@ public class BarChart04 implements ExampleChart { // Create Chart Chart chart = new ChartBuilder().chartType(ChartType.Bar).width(800).height(600).title("XFactor vs. Age").xAxisTitle("Age").yAxisTitle("XFactor").build(); chart.addSeries("female", new double[] { 10, 20, 30, 40, 50 }, new double[] { 50, 10, 20, 40, 35 }); - chart.addSeries("male", new double[] { 10, 20, 30, 50 }, new double[] { 40, 30, 20, 60 }); + chart.addSeries("male", new double[] { 10, 20, 30, 40, 50 }, new double[] { 40, 30, 20, 0, 60 }); chart.getStyleManager().setYAxisMin(5); chart.getStyleManager().setYAxisMax(70); diff --git a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart06.java b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart06.java new file mode 100644 index 0000000000000000000000000000000000000000..69dc6b8d4451362996b266bda8f4f3fb9c3f833f --- /dev/null +++ b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart06.java @@ -0,0 +1,77 @@ +/** + * Copyright 2011 - 2014 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.bar; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import com.xeiam.xchart.Chart; +import com.xeiam.xchart.ChartBuilder; +import com.xeiam.xchart.Histogram; +import com.xeiam.xchart.StyleManager.ChartType; +import com.xeiam.xchart.StyleManager.LegendPosition; +import com.xeiam.xchart.SwingWrapper; +import com.xeiam.xchart.demo.charts.ExampleChart; + +/** + * Histogram Overlapped + * <p> + * Demonstrates the following: + * <ul> + * <li>Histogram + * <li>Bar Chart styles - overlapped, bar width + */ +public class BarChart06 implements ExampleChart { + + public static void main(String[] args) { + + ExampleChart exampleChart = new BarChart06(); + Chart chart = exampleChart.getChart(); + new SwingWrapper(chart).displayChart(); + } + + @Override + public Chart getChart() { + + // Create Chart + Chart chart = new ChartBuilder().chartType(ChartType.Bar).width(800).height(600).title("Score Histogram").xAxisTitle("Mean").yAxisTitle("Count").build(); + + Histogram histogram1 = new Histogram(getGaussianData(10000), 30, -30, 30); + Histogram histogram2 = new Histogram(getGaussianData(5000), 30, -30, 30); + chart.addSeries("histogram 1", histogram1.getxAxisData(), histogram1.getyAxisData()); + chart.addSeries("histogram 2", histogram2.getxAxisData(), histogram2.getyAxisData()); + + // Customize Chart + chart.getStyleManager().setLegendPosition(LegendPosition.InsideNW); + chart.getStyleManager().setBarWidthPercentage(.96); + chart.getStyleManager().setBarsOverlapped(true); + + return chart; + } + + private List<Double> getGaussianData(int count) { + + List<Double> data = new ArrayList<Double>(count); + Random r = new Random(); + for (int i = 0; i < count; i++) { + data.add(r.nextGaussian() * 10); + // data.add(r.nextDouble() * 60 - 30); + } + return data; + } + +} diff --git a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart07.java b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart07.java new file mode 100644 index 0000000000000000000000000000000000000000..3b156ee55912e8c80fc19dc5e52f90b6933b75d9 --- /dev/null +++ b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/bar/BarChart07.java @@ -0,0 +1,77 @@ +/** + * Copyright 2011 - 2014 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.bar; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import com.xeiam.xchart.Chart; +import com.xeiam.xchart.ChartBuilder; +import com.xeiam.xchart.Histogram; +import com.xeiam.xchart.StyleManager.ChartType; +import com.xeiam.xchart.StyleManager.LegendPosition; +import com.xeiam.xchart.SwingWrapper; +import com.xeiam.xchart.demo.charts.ExampleChart; + +/** + * Histogram Not Overlapped + * <p> + * Demonstrates the following: + * <ul> + * <li>Histogram + * <li>Bar Chart styles - not overlapped, bar width + * <li>Integer data values + */ +public class BarChart07 implements ExampleChart { + + public static void main(String[] args) { + + ExampleChart exampleChart = new BarChart07(); + Chart chart = exampleChart.getChart(); + new SwingWrapper(chart).displayChart(); + } + + @Override + public Chart getChart() { + + // Create Chart + Chart chart = new ChartBuilder().chartType(ChartType.Bar).width(800).height(600).title("Score Histogram").xAxisTitle("Mean").yAxisTitle("Count").build(); + + Histogram histogram1 = new Histogram(getGaussianData(1000), 10, -30, 30); + chart.addSeries("histogram 1", histogram1.getxAxisData(), histogram1.getyAxisData()); + Histogram histogram2 = new Histogram(getGaussianData(1000), 10, -30, 30); + chart.addSeries("histogram 2", histogram2.getxAxisData(), histogram2.getyAxisData()); + + // Customize Chart + chart.getStyleManager().setLegendPosition(LegendPosition.InsideNW); + chart.getStyleManager().setBarWidthPercentage(.96); + + return chart; + } + + private List<Integer> getGaussianData(int count) { + + List<Integer> data = new ArrayList<Integer>(count); + Random r = new Random(); + for (int i = 0; i < count; i++) { + data.add((int) (r.nextGaussian() * 10)); + // data.add(r.nextDouble() * 60 - 30); + } + return data; + } + +} diff --git a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart06.java b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart06.java index c8121517647a81a08eba7fc9d5a515e6ad645db3..32a83d020fc50f351cba47c0fcf5ecc06463a4e3 100644 --- a/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart06.java +++ b/xchart-demo/src/main/java/com/xeiam/xchart/demo/charts/line/LineChart06.java @@ -46,11 +46,11 @@ public class LineChart06 implements ExampleChart { @Override public Chart getChart() { - double[] xData = new double[] { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 }; + int[] xData = new int[] { 0, 1, 2, 3, 4, 5, 6 }; - double[] yData1 = new double[] { 100, 100, 100, 60, 10, 10, 10 }; + int[] yData1 = new int[] { 100, 100, 100, 60, 10, 10, 10 }; - double[] errdata = new double[] { 50, 20, 10, 52, 9, 2, 1 }; + int[] errdata = new int[] { 50, 20, 10, 52, 9, 2, 1 }; Chart chart = new Chart(800, 600); diff --git a/xchart/pom.xml b/xchart/pom.xml index 62e9feceb0a2beab0003524a55026187690382ac..23fb06a167859d37af875829a9b5a67d5b2fea20 100644 --- a/xchart/pom.xml +++ b/xchart/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>com.xeiam.xchart</groupId> <artifactId>xchart-parent</artifactId> - <version>2.3.2-SNAPSHOT</version> + <version>2.3.3-SNAPSHOT</version> </parent> <artifactId>xchart</artifactId> diff --git a/xchart/src/main/java/com/xeiam/xchart/Chart.java b/xchart/src/main/java/com/xeiam/xchart/Chart.java index 7eb04bae693dd9d00c132f0752d9eb84aeb1e4e4..8b26ee6d833726e3c5142e0432ce62ff41b1a24b 100644 --- a/xchart/src/main/java/com/xeiam/xchart/Chart.java +++ b/xchart/src/main/java/com/xeiam/xchart/Chart.java @@ -43,7 +43,6 @@ public class Chart { public Chart(int width, int height) { chartPainter = new ChartPainter(width, height); - } /** @@ -176,6 +175,52 @@ public class Chart { return chartPainter.getAxisPair().addSeries(seriesName, xDataNumber, yDataNumber, errorBarDataNumber); } + /** + * Add a series to the chart using int arrays + * + * @param seriesName + * @param xData the X-Axis data + * @param xData the Y-Axis data + * @return A Series object that you can set properties on + */ + public Series addSeries(String seriesName, int[] xData, int[] yData) { + + return addSeries(seriesName, xData, yData, null); + } + + /** + * Add a series to the chart using int arrays with error bars + * + * @param seriesName + * @param xData the X-Axis data + * @param xData the Y-Axis data + * @param errorBars the error bar data + * @return A Series object that you can set properties on + */ + public Series addSeries(String seriesName, int[] xData, int[] yData, int[] errorBars) { + + List<Double> xDataNumber = null; + if (xData != null) { + xDataNumber = new ArrayList<Double>(); + for (int d : xData) { + xDataNumber.add(new Double(d)); + } + } + List<Double> yDataNumber = new ArrayList<Double>(); + for (int d : yData) { + yDataNumber.add(new Double(d)); + } + List<Double> errorBarDataNumber = null; + if (errorBars != null) { + errorBarDataNumber = new ArrayList<Double>(); + for (int d : errorBars) { + errorBarDataNumber.add(new Double(d)); + } + } + + return chartPainter.getAxisPair().addSeries(seriesName, xDataNumber, yDataNumber, errorBarDataNumber); + } + /** * Set the chart title * diff --git a/xchart/src/main/java/com/xeiam/xchart/Histogram.java b/xchart/src/main/java/com/xeiam/xchart/Histogram.java new file mode 100644 index 0000000000000000000000000000000000000000..38b2a90e31a11975b5ecf627f27b5ba8c8cf979c --- /dev/null +++ b/xchart/src/main/java/com/xeiam/xchart/Histogram.java @@ -0,0 +1,142 @@ +/** + * Copyright 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * This class can be used to create histogram data for histogram bar charts + * + * @author timmolter + */ +public class Histogram { + + private List<Double> xAxisData; // bin centers + private List<Double> yAxisData; // frequency counts + private final Collection<? extends Number> originalData; + private final int numBins; + private final double min; + private final double max; + + /** + * Constructor + * + * @param data + * @param numBins + */ + public Histogram(Collection<? extends Number> data, int numBins) { + + this.numBins = numBins; + this.originalData = data; + + List<Double> dataAsList = new ArrayList<Double>(); + Iterator<? extends Number> itr = data.iterator(); + while (itr.hasNext()) { + dataAsList.add(((Number) itr.next()).doubleValue()); + } + Collections.sort(dataAsList); + this.min = dataAsList.get(0); + this.max = dataAsList.get(dataAsList.size() - 1); + + init(); + } + + /** + * Constructor + * + * @param data + * @param numBins + * @param min + * @param max + */ + public Histogram(Collection<? extends Number> data, int numBins, double min, double max) { + + this.numBins = numBins; + this.originalData = data; + // Arrays.sort(data); + this.min = min; + this.max = max; + // this.min = data[0]; + // this.max = data[data.length - 1]; + + init(); + } + + private void init() { + + double[] tempYAxisData = new double[numBins]; + final double binSize = (max - min) / numBins; + + // y axis data + Iterator<? extends Number> itr = originalData.iterator(); + while (itr.hasNext()) { + + int bin = (int) ((((Number) itr.next()).doubleValue() - min) / binSize); // changed this from numBins + if (bin < 0) { /* this data is smaller than min */ + } + else if (bin >= numBins) { /* this data point is bigger than max */ + } + else { + tempYAxisData[bin] += 1; + } + } + yAxisData = new ArrayList<Double>(numBins); + for (double d : tempYAxisData) { + yAxisData.add(new Double(d)); + } + + // x axis data + xAxisData = new ArrayList<Double>(numBins); + for (int i = 0; i < numBins; i++) { + xAxisData.add(((i * (max - min)) / numBins + min) + binSize / 2); + } + } + + public List<Double> getxAxisData() { + + return xAxisData; + } + + public List<Double> getyAxisData() { + + return yAxisData; + } + + public Collection<? extends Number> getOriginalData() { + + return originalData; + } + + public int getNumBins() { + + return numBins; + } + + public double getMin() { + + return min; + } + + public double getMax() { + + return max; + } + +} diff --git a/xchart/src/main/java/com/xeiam/xchart/StyleManager.java b/xchart/src/main/java/com/xeiam/xchart/StyleManager.java index 452f3096e3f71f6a028a0c76e981caeca3901b90..5b50fa7965ded5c1a0b952a828130327d76e5969 100644 --- a/xchart/src/main/java/com/xeiam/xchart/StyleManager.java +++ b/xchart/src/main/java/com/xeiam/xchart/StyleManager.java @@ -116,7 +116,7 @@ public class StyleManager { private Double xAxisMax; private Double yAxisMin; private Double yAxisMax; - private double axisTickSpaceRatio; + private double axisTickSpaceRatio; // Chart Plot Area /////////////////////////////// private boolean isPlotGridLinesVisible; @@ -127,6 +127,10 @@ public class StyleManager { private Color plotGridLinesColor; private Stroke plotGridLinesStroke; + // Bar Charts /////////////////////////////// + private double barWidthPercentage; + private boolean barsOverlapped; + // Error Bars /////////////////////////////// private Color errorBarsColor; @@ -205,6 +209,10 @@ public class StyleManager { plotGridLinesColor = theme.getPlotGridLinesColor(); plotGridLinesStroke = theme.getPlotGridLinesStroke(); + // Bar Charts /////////////////////////////// + barWidthPercentage = theme.getBarWidthPercentage(); + barsOverlapped = theme.barsOverlapped(); + // Error Bars /////////////////////////////// errorBarsColor = theme.getErrorBarsColor(); @@ -850,11 +858,13 @@ public class StyleManager { } public void setAxisTickSpaceRatio(double axisTickSpaceRatio) { + this.axisTickSpaceRatio = axisTickSpaceRatio; } public double getAxisTickSpaceRatio() { - return axisTickSpaceRatio; + + return axisTickSpaceRatio; } // Chart Plot Area /////////////////////////////// @@ -964,6 +974,38 @@ public class StyleManager { return plotGridLinesStroke; } + // Bar Charts /////////////////////////////// + + /** + * set the width of a single bar in a bar chart. full width is 100%, i.e. 1.0 + * + * @param barWidthPercentage + */ + public void setBarWidthPercentage(double barWidthPercentage) { + + this.barWidthPercentage = barWidthPercentage; + } + + public double getBarWidthPercentage() { + + return barWidthPercentage; + } + + /** + * set whether or no bars are overlapped. Otherwise they are places side-by-side + * + * @param barsOverlapped + */ + public void setBarsOverlapped(boolean barsOverlapped) { + + this.barsOverlapped = barsOverlapped; + } + + public boolean barsOverlapped() { + + return barsOverlapped; + } + // Error Bars /////////////////////////////// /** diff --git a/xchart/src/main/java/com/xeiam/xchart/SwingWrapper.java b/xchart/src/main/java/com/xeiam/xchart/SwingWrapper.java index 5f51cf89306e7853db98853bcba960748de7e62d..14d2ae08211559e3e041c4a2ec8105f0dc89515e 100644 --- a/xchart/src/main/java/com/xeiam/xchart/SwingWrapper.java +++ b/xchart/src/main/java/com/xeiam/xchart/SwingWrapper.java @@ -29,6 +29,8 @@ import javax.swing.JPanel; */ public class SwingWrapper { + private String windowTitle = "XChart"; + private List<Chart> charts = new ArrayList<Chart>(); private int numRows; private int numColumns; @@ -70,13 +72,25 @@ public class SwingWrapper { this.numColumns = numColumns; } + /** + * Display the chart in a Swing JFrame + * + * @param windowTitle the title of the window + */ + public JFrame displayChart(String windowTitle) { + + this.windowTitle = windowTitle; + + return displayChart(); + } + /** * Display the chart in a Swing JFrame */ public JFrame displayChart() { // Create and set up the window. - final JFrame frame = new JFrame("XChart"); + final JFrame frame = new JFrame(windowTitle); // Schedule a job for the event-dispatching thread: // creating and showing this application's GUI. @@ -98,13 +112,26 @@ public class SwingWrapper { return frame; } + /** + * Display the charts in a Swing JFrame + * + * @param windowTitle the title of the window + * @return the JFrame + */ + public JFrame displayChartMatrix(String windowTitle) { + + this.windowTitle = windowTitle; + + return displayChartMatrix(); + } + /** * Display the chart in a Swing JFrame */ public JFrame displayChartMatrix() { // Create and set up the window. - final JFrame frame = new JFrame("XChart"); + final JFrame frame = new JFrame(windowTitle); // Schedule a job for the event-dispatching thread: // creating and showing this application's GUI. diff --git a/xchart/src/main/java/com/xeiam/xchart/XChartPanel.java b/xchart/src/main/java/com/xeiam/xchart/XChartPanel.java index c2c9eedfd7e8df8a0d71fcf56fc1ad2cd94486f8..545e28764bcc1f09d705eb10f118443e71ce2a1c 100644 --- a/xchart/src/main/java/com/xeiam/xchart/XChartPanel.java +++ b/xchart/src/main/java/com/xeiam/xchart/XChartPanel.java @@ -245,7 +245,7 @@ public class XChartPanel extends JPanel { } /** - * update a series by only updating the Y-Axis data. The X-Axis data will is automatically generated as a list of increasing Integers starting from 1 and ending at the size of the new Y-Axis data + * update a series by only updating the Y-Axis data. The X-Axis data will be automatically generated as a list of increasing Integers starting from 1 and ending at the size of the new Y-Axis data * list. * * @param seriesName @@ -275,7 +275,7 @@ public class XChartPanel extends JPanel { } /** - * update a series by only updating both the X-Axis and Y-Axis data + * update a series by updating both the X-Axis and Y-Axis data * * @param seriesName * @param newYData diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/Utils.java b/xchart/src/main/java/com/xeiam/xchart/internal/Utils.java index 8bceebf4b0fa5db46b118d264e616c1a60b28c0d..eb310e31c7305b1320126164035fab2b1e2d7c86 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/Utils.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/Utils.java @@ -15,7 +15,6 @@ */ package com.xeiam.xchart.internal; - /** * @author timmolter */ @@ -35,10 +34,10 @@ public class Utils { * @param tickSpace * @return */ - public static int getTickStartOffset(int workingSpace, int tickSpace) { + public static double getTickStartOffset(double workingSpace, double tickSpace) { - int marginSpace = workingSpace - tickSpace; - return (int) (marginSpace / 2.0); + double marginSpace = workingSpace - tickSpace; + return marginSpace / 2.0; } public static double pow(double base, int exponent) { 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 fef1bda9dacaabdfcad169c4bbb08cc108d722ba..e3bc3a42fcc28de4325700d7c41b56ada923b32d 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 @@ -102,9 +102,9 @@ public class AxisPair implements ChartPart { series = new Series(seriesName, xData, xAxis.getAxisType(), yData, yAxis.getAxisType(), errorBars, seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle()); } else { // generate xData - List<Number> generatedXData = new ArrayList<Number>(); + List<Double> generatedXData = new ArrayList<Double>(); for (int i = 1; i < yData.size() + 1; i++) { - generatedXData.add(i); + generatedXData.add((double) i); } xAxis.setAxisType(AxisType.Number); yAxis.setAxisType(AxisType.Number); 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 c21536f5767fdcdbdcb1b2dbf31104bb409470e7..86689299279bd6b3cf8cd0d924a43799d7d177f6 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 @@ -152,7 +152,7 @@ public class AxisTick implements ChartPart { return axisTickLabels; } - public List<Integer> getTickLocations() { + public List<Double> getTickLocations() { return gridStep.getTickLocations(); } diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickBarChartCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickBarChartCalculator.java index e163b67852d693c58f0f1216cc7457fc4560953c..565e2e27f2c905c3cc5717f380961149c5387a75 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickBarChartCalculator.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickBarChartCalculator.java @@ -53,23 +53,34 @@ public class AxisTickBarChartCalculator extends AxisTickCalculator { int tickSpace = (int) (styleManager.getAxisTickSpaceRatio() * workingSpace); // in plot space // where the tick should begin in the working space in pixels - int margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace); + double margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace); // get all categories List<Object> categories = new ArrayList<Object>(); + + Series firstSeries = chartPainter.getAxisPair().getSeriesMap().values().iterator().next(); // we use this to check all series have the exact same length and values for (Series series : chartPainter.getAxisPair().getSeriesMap().values()) { + Iterator<?> firstSeriesItr = firstSeries.getXData().iterator(); Iterator<?> xItr = series.getXData().iterator(); while (xItr.hasNext()) { + + // check matching + Object next = xItr.next(); + Object firstSeriesNext = firstSeriesItr.next(); + if (!firstSeriesNext.equals(next)) { + throw new IllegalArgumentException("X-Axis data must exactly match all other Series X-Axis data for Bar Charts!!"); + } + Object x = null; if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Number) { - x = xItr.next(); + x = next; } else if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Date) { - x = (double) (((Date) xItr.next()).getTime()); + x = (double) (((Date) next).getTime()); } else if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.String) { - x = xItr.next(); + x = next; } if (!categories.contains(x)) { categories.add(x); @@ -77,37 +88,79 @@ public class AxisTickBarChartCalculator extends AxisTickCalculator { } } - int numCategories = categories.size(); + if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.String) { - int gridStep = (int) (tickSpace / (double) numCategories); - int firstPosition = (int) (gridStep / 2.0); + double gridStep = (tickSpace / (double) categories.size()); + double firstPosition = gridStep / 2.0; + int counter = 0; + for (Object category : categories) { + tickLabels.add(category.toString()); + double tickLabelPosition = margin + firstPosition + gridStep * counter++; + tickLocations.add(tickLabelPosition); + } + } + else if (categories.size() < 13) { // Number or Date and 12 or less categories. give each category a tick label - // generate all tickLabels and tickLocations from the first to last position - NumberFormatter numberFormatter = null; - DateFormatter dateFormatter = null; + double gridStep = (tickSpace / (double) categories.size()); + double firstPosition = gridStep / 2.0; - if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Number) { - numberFormatter = new NumberFormatter(styleManager); - } - else if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Date) { - dateFormatter = new DateFormatter(chartPainter.getStyleManager()); + // generate all tickLabels and tickLocations from the first to last position + NumberFormatter numberFormatter = null; + DateFormatter dateFormatter = null; + + if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Number) { + numberFormatter = new NumberFormatter(styleManager); + } + else if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Date) { + dateFormatter = new DateFormatter(chartPainter.getStyleManager()); + } + int counter = 0; + + for (Object category : categories) { + if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Number) { + tickLabels.add(numberFormatter.formatNumber((Double) category)); + } + else if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Date) { + long span = (long) Math.abs(maxValue - minValue); // in data space + long gridStepHint = (long) (span / (double) tickSpace * styleManager.getXAxisTickMarkSpacingHint()); + long timeUnit = dateFormatter.getTimeUnit(gridStepHint); + tickLabels.add(dateFormatter.formatDate((Double) category, timeUnit)); + } + double tickLabelPosition = (int) (margin + firstPosition + gridStep * counter++); + tickLocations.add(tickLabelPosition); + } } - int counter = 0; - for (Object category : categories) { + else { // Number or Date and more than 12 categories. divide up the axis tick space according to normal number axis layout + + double gridStep = getNumericalGridStep(tickSpace); + double firstPosition = getFirstPosition(gridStep); + + // generate all tickLabels and tickLocations from the first to last position + NumberFormatter numberFormatter = null; + DateFormatter dateFormatter = null; + if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Number) { - tickLabels.add(numberFormatter.formatNumber((Double) category)); + numberFormatter = new NumberFormatter(styleManager); } else if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Date) { - long span = (long) Math.abs(maxValue - minValue); // in data space - long gridStepHint = (long) (span / (double) tickSpace * styleManager.getXAxisTickMarkSpacingHint()); - long timeUnit = dateFormatter.getTimeUnit(gridStepHint); - tickLabels.add(dateFormatter.formatDate((Double) category, timeUnit)); + dateFormatter = new DateFormatter(chartPainter.getStyleManager()); } - else if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.String) { - tickLabels.add(category.toString()); + + for (double tickPosition = firstPosition; tickPosition <= maxValue; tickPosition = tickPosition + gridStep) { + + if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Number) { + tickLabels.add(numberFormatter.formatNumber(tickPosition)); + } + else if (chartPainter.getAxisPair().getXAxis().getAxisType() == AxisType.Date) { + long span = (long) Math.abs(maxValue - minValue); // in data space + long gridStepHint = (long) (span / (double) tickSpace * styleManager.getXAxisTickMarkSpacingHint()); + long timeUnit = dateFormatter.getTimeUnit(gridStepHint); + tickLabels.add(dateFormatter.formatDate(tickPosition, timeUnit)); + } + double tickLabelPosition = margin + ((tickPosition - minValue) / (maxValue - minValue) * tickSpace); + tickLocations.add(tickLabelPosition); } - int tickLabelPosition = margin + firstPosition + gridStep * counter++; - tickLocations.add(tickLabelPosition); } + } } diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickCalculator.java index 02ea5497f0fddf89b7f75ddeb5186cf3604d34c2..e3077239628bb88aeea149babc9680a9e2ee287e 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickCalculator.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickCalculator.java @@ -29,7 +29,7 @@ import com.xeiam.xchart.internal.chartpart.Axis.Direction; public abstract class AxisTickCalculator { /** the List of tick label position in pixels */ - protected List<Integer> tickLocations = new LinkedList<Integer>();; + protected List<Double> tickLocations = new LinkedList<Double>();; /** the List of tick label values */ protected List<String> tickLabels = new LinkedList<String>(); @@ -110,7 +110,7 @@ public abstract class AxisTickCalculator { return firstPosition; } - public List<Integer> getTickLocations() { + public List<Double> getTickLocations() { return tickLocations; } @@ -120,4 +120,70 @@ public abstract class AxisTickCalculator { return tickLabels; } + /** + * Determine the grid step for the data set given the space in pixels allocated for the axis + * + * @param tickSpace in plot space + * @return + */ + public double getNumericalGridStep(double tickSpace) { + + // this prevents an infinite loop when the plot gets sized really small. + if (tickSpace < 10) { + return 1.0; + } + + // the span of the data + double span = Math.abs(maxValue - minValue); // in data space + + int tickMarkSpaceHint = (axisDirection == Direction.X ? styleManager.getXAxisTickMarkSpacingHint() : styleManager.getYAxisTickMarkSpacingHint()); + + // 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 == Double.NEGATIVE_INFINITY) { + significand /= 10.0; + exponent++; + } + } + + // calculate the grid step with hint. + double gridStep; + if (significand > 7.5) { + // gridStep = 10.0 * 10 ** exponent + gridStep = 10.0 * Utils.pow(10, exponent); + } + else if (significand > 3.5) { + // gridStep = 5.0 * 10 ** exponent + gridStep = 5.0 * Utils.pow(10, exponent); + } + else if (significand > 1.5) { + // gridStep = 2.0 * 10 ** exponent + gridStep = 2.0 * Utils.pow(10, exponent); + } + else { + // gridStep = 1.0 * 10 ** exponent + gridStep = Utils.pow(10, exponent); + } + return gridStep; + } + } diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickDateCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickDateCalculator.java index 512b15e23d60cc3ba8ba6816070840b852c3c099..a981741591c001f498d548f9cc24680564fa54b8 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickDateCalculator.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickDateCalculator.java @@ -47,10 +47,10 @@ public class AxisTickDateCalculator extends AxisTickCalculator { private void calculate() { // tick space - a percentage of the working space available for ticks - int tickSpace = (int)(styleManager.getAxisTickSpaceRatio() * workingSpace); // in plot space + int tickSpace = (int) (styleManager.getAxisTickSpaceRatio() * workingSpace); // in plot space // where the tick should begin in the working space in pixels - int margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace); + double margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace); // the span of the data long span = (long) Math.abs(maxValue - minValue); // in data space @@ -74,7 +74,7 @@ public class AxisTickDateCalculator extends AxisTickCalculator { tickLabels.add(dateFormatter.formatDate(tickPosition, timeUnit)); // here we convert tickPosition finally to plot space, i.e. pixels - int tickLabelPosition = (int) (margin + ((tickPosition - minValue) / (maxValue - minValue) * tickSpace)); + double tickLabelPosition = (int) (margin + ((tickPosition - minValue) / (maxValue - minValue) * tickSpace)); tickLocations.add(tickLabelPosition); } } diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLabels.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLabels.java index 6beba45d9c363b35177410f7468db6a116237f75..5b851847dfb8c08cf7b00acd8859f67e49441d9b 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLabels.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLabels.java @@ -63,7 +63,7 @@ public class AxisTickLabels implements ChartPart { String tickLabel = axisTick.getTickLabels().get(i); // System.out.println(tickLabel); - int tickLocation = axisTick.getTickLocations().get(i); + double tickLocation = axisTick.getTickLocations().get(i); if (tickLabel != null) { // some are null for logarithmic axes @@ -100,7 +100,7 @@ public class AxisTickLabels implements ChartPart { for (int i = 0; i < axisTick.getTickLabels().size(); i++) { String tickLabel = axisTick.getTickLabels().get(i); - int tickLocation = axisTick.getTickLocations().get(i); + double tickLocation = axisTick.getTickLocations().get(i); if (tickLabel != null) { // some are null for logarithmic axes FontRenderContext frc = g.getFontRenderContext(); diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLogarithmicCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLogarithmicCalculator.java index db3f0cbe9a31d15ec90d65c790681fa3882a62d9..4f8146a9e8d835939ccff04e477ffed1eece72df 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLogarithmicCalculator.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickLogarithmicCalculator.java @@ -49,15 +49,15 @@ public class AxisTickLogarithmicCalculator extends AxisTickCalculator { // a check if all axis data are the exact same values if (minValue == maxValue) { tickLabels.add(numberFormatter.formatNumber(maxValue)); - tickLocations.add((int) (workingSpace / 2.0)); + tickLocations.add(workingSpace / 2.0); return; } // tick space - a percentage of the working space available for ticks - int tickSpace = (int) (styleManager.getAxisTickSpaceRatio() * workingSpace); // in plot space + double tickSpace = styleManager.getAxisTickSpaceRatio() * workingSpace; // in plot space // where the tick should begin in the working space in pixels - int margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace); + double margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace); int logMin = (int) Math.floor(Math.log10(minValue)); int logMax = (int) Math.ceil(Math.log10(maxValue)); @@ -93,10 +93,11 @@ public class AxisTickLogarithmicCalculator extends AxisTickCalculator { // System.out.println("i: " + i); // System.out.println("pow(10, i).doubleValue(): " + pow(10, i).doubleValue()); - for (double j = firstPosition; j <= Utils.pow(10, i); j = j + tickStep) { + // using trhe .00000001 factor to dal with double value imprecision + for (double j = firstPosition; j <= Utils.pow(10, i) + .00000001; j = j + tickStep) { // System.out.println("j: " + j); - // System.out.println(Math.log10(j.doubleValue()) % 1); + // System.out.println(Math.log10(j) % 1); if (j < minValue) { // System.out.println("continue"); @@ -117,7 +118,7 @@ public class AxisTickLogarithmicCalculator extends AxisTickCalculator { } // add all the tick marks though - int tickLabelPosition = (int) (margin + (Math.log10(j) - Math.log10(minValue)) / (Math.log10(maxValue) - Math.log10(minValue)) * tickSpace); + double tickLabelPosition = (int) (margin + (Math.log10(j) - Math.log10(minValue)) / (Math.log10(maxValue) - Math.log10(minValue)) * tickSpace); tickLocations.add(tickLabelPosition); } tickStep = tickStep * Utils.pow(10, 1); diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickMarks.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickMarks.java index 5d81030072dc9fb5029e0c1c6d01b63a5b2a113b..9dc312310dfbc8aebc6b3cec8e6d9874d1b33a18 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickMarks.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickMarks.java @@ -63,7 +63,7 @@ public class AxisTickMarks implements ChartPart { for (int i = 0; i < axisTick.getTickLabels().size(); i++) { - int tickLocation = axisTick.getTickLocations().get(i); + double tickLocation = axisTick.getTickLocations().get(i); Shape line = new Line2D.Double(xOffset, yOffset + axisTick.getAxis().getPaintZone().getHeight() - tickLocation, xOffset + getChartPainter().getStyleManager().getAxisTickMarkLength(), yOffset @@ -98,7 +98,7 @@ public class AxisTickMarks implements ChartPart { for (int i = 0; i < axisTick.getTickLabels().size(); i++) { - int tickLocation = axisTick.getTickLocations().get(i); + double tickLocation = axisTick.getTickLocations().get(i); Shape line = new Line2D.Double(xOffset + tickLocation, yOffset, xOffset + tickLocation, yOffset - getChartPainter().getStyleManager().getAxisTickMarkLength()); g.draw(line); diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickNumericalCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickNumericalCalculator.java index 85e296b640fdafd7f2014994818393e543f48ae4..58a1aa275741ecd1d312c09b193a2c8adf39d0d1 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickNumericalCalculator.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickNumericalCalculator.java @@ -15,6 +15,8 @@ */ package com.xeiam.xchart.internal.chartpart; +import java.math.BigDecimal; + import com.xeiam.xchart.StyleManager; import com.xeiam.xchart.internal.Utils; import com.xeiam.xchart.internal.chartpart.Axis.Direction; @@ -49,92 +51,27 @@ public class AxisTickNumericalCalculator extends AxisTickCalculator { // a check if all axis data are the exact same values if (minValue == maxValue) { tickLabels.add(numberFormatter.formatNumber(maxValue)); - tickLocations.add((int) (workingSpace / 2.0)); + tickLocations.add(workingSpace / 2.0); return; } // tick space - a percentage of the working space available for ticks - int tickSpace = (int)(styleManager.getAxisTickSpaceRatio() * workingSpace); // in plot space + double tickSpace = styleManager.getAxisTickSpaceRatio() * workingSpace; // in plot space // where the tick should begin in the working space in pixels - int margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace); + double margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace); - double gridStep = getGridStep(tickSpace); - double firstPosition = getFirstPosition(gridStep); + BigDecimal gridStep = BigDecimal.valueOf(getNumericalGridStep(tickSpace)); + BigDecimal firstPosition = BigDecimal.valueOf(getFirstPosition(gridStep.doubleValue())); // generate all tickLabels and tickLocations from the first to last position - for (double tickPosition = firstPosition; tickPosition <= maxValue; tickPosition = tickPosition + gridStep) { + for (BigDecimal tickPosition = firstPosition; tickPosition.compareTo(BigDecimal.valueOf(maxValue)) <= 0; tickPosition = tickPosition.add(gridStep)) { - tickLabels.add(numberFormatter.formatNumber(tickPosition)); + tickLabels.add(numberFormatter.formatNumber(tickPosition.doubleValue())); // here we convert tickPosition finally to plot space, i.e. pixels - int tickLabelPosition = (int) (margin + ((tickPosition - minValue) / (maxValue - minValue) * tickSpace)); + double tickLabelPosition = margin + ((tickPosition.doubleValue() - minValue) / (maxValue - minValue) * 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 double getGridStep(int tickSpace) { - - // this prevents an infinite loop when the plot gets sized really small. - if (tickSpace < 10) { - return 1.0; - } - - // the span of the data - double span = Math.abs(maxValue - minValue); // in data space - - int tickMarkSpaceHint = (axisDirection == Direction.X ? styleManager.getXAxisTickMarkSpacingHint() : styleManager.getYAxisTickMarkSpacingHint()); - - // 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 == Double.NEGATIVE_INFINITY) { - significand /= 10.0; - exponent++; - } - } - - // calculate the grid step with hint. - double gridStep; - if (significand > 7.5) { - // gridStep = 10.0 * 10 ** exponent - gridStep = 10.0 * Utils.pow(10, exponent); - } - else if (significand > 3.5) { - // gridStep = 5.0 * 10 ** exponent - gridStep = 5.0 * Utils.pow(10, exponent); - } - else if (significand > 1.5) { - // gridStep = 2.0 * 10 ** exponent - gridStep = 2.0 * Utils.pow(10, exponent); - } - else { - // gridStep = 1.0 * 10 ** exponent - gridStep = Utils.pow(10, exponent); - } - return gridStep; - } } diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentBarChart.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentBarChart.java index 7fc2e0e3e3d0d7d826e7f7713c9a8727dbe34465..318e793c51f523ea17483a561aaf39cad05152b5 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentBarChart.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentBarChart.java @@ -18,10 +18,8 @@ package com.xeiam.xchart.internal.chartpart; import java.awt.Graphics2D; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; -import java.util.List; import com.xeiam.xchart.Series; import com.xeiam.xchart.StyleManager; @@ -49,27 +47,15 @@ public class PlotContentBarChart extends PlotContent { StyleManager styleManager = plot.getChartPainter().getStyleManager(); // X-Axis - int xTickSpace = (int) (styleManager.getAxisTickSpaceRatio() * bounds.getWidth()); - int xLeftMargin = Utils.getTickStartOffset((int) bounds.getWidth(), xTickSpace); + double xTickSpace = styleManager.getAxisTickSpaceRatio() * bounds.getWidth(); + double xLeftMargin = Utils.getTickStartOffset(bounds.getWidth(), xTickSpace); // Y-Axis - int yTickSpace = (int) (styleManager.getAxisTickSpaceRatio() * bounds.getHeight()); - int yTopMargin = Utils.getTickStartOffset((int) bounds.getHeight(), yTickSpace); + double yTickSpace = styleManager.getAxisTickSpaceRatio() * bounds.getHeight(); + double yTopMargin = Utils.getTickStartOffset(bounds.getHeight(), yTickSpace); - // get all categories - List<Object> categories = new ArrayList<Object>(); - for (Series series : getChartPainter().getAxisPair().getSeriesMap().values()) { - - Iterator<?> xItr = series.getXData().iterator(); - while (xItr.hasNext()) { - Object object = xItr.next(); - if (!categories.contains(object)) { - categories.add(object); - } - } - } - int numBars = categories.size(); - int gridStep = (int) (xTickSpace / (double) numBars); + int numBars = getChartPainter().getAxisPair().getSeriesMap().values().iterator().next().getXData().size(); + double gridStep = xTickSpace / numBars; // plot series int seriesCounter = 0; @@ -97,7 +83,7 @@ public class PlotContentBarChart extends PlotContent { } else if (getChartPainter().getStyleManager().isYAxisLogarithmic()) { // int logMin = (int) Math.floor(Math.log10(getChartPainter().getAxisPair().getyAxis().getMin().doubleValue())); - int logMin = (int) Math.floor(Math.log10(getChartPainter().getAxisPair().getYAxis().getMin())); + double logMin = Math.floor(Math.log10(getChartPainter().getAxisPair().getYAxis().getMin())); // System.out.println("logMin: " + logMin); // System.out.println("min : " + getChartPainter().getAxisPair().getyAxis().getMin().doubleValue()); yMin = logMin; @@ -122,70 +108,90 @@ public class PlotContentBarChart extends PlotContent { // System.out.println(yMin); // System.out.println(yMax); - Iterator<?> categoryItr = categories.iterator(); + // all the x-axis data are guaranteed to be the same so we just use the first one Iterator<? extends Number> yItr = yData.iterator(); int barCounter = 0; - while (categoryItr.hasNext()) { + while (yItr.hasNext()) { + + double y = ((Number) yItr.next()).doubleValue(); + if (getChartPainter().getStyleManager().isYAxisLogarithmic()) { + y = Math.log10(y); + } + + double yTop = 0.0; + double yBottom = 0.0; - if (xData.contains(categoryItr.next())) { + switch (chartForm) { + case 1: // positive chart - double y = ((Number) yItr.next()).doubleValue(); - if (getChartPainter().getStyleManager().isYAxisLogarithmic()) { - y = Math.log10(y); + // check for points off the chart draw area due to a custom yMin + if (y < yMin) { + barCounter++; + continue; } - double yTop = 0.0; - double yBottom = 0.0; + yTop = y; + yBottom = yMin; + break; + case -1: // negative chart - switch (chartForm) { - case 1: // positive chart + // check for points off the chart draw area due to a custom yMin + if (y > yMax) { + barCounter++; + continue; + } + + yTop = yMax; + yBottom = y; + break; + case 0: // span chart + if (y >= 0.0) { // positive yTop = y; - yBottom = yMin; - break; - case -1: // negative chart - yTop = yMax; + yBottom = 0.0; + } + else { + yTop = 0.0; yBottom = y; - break; - case 0: // span chart - if (y >= 0.0) { // positive - yTop = y; - yBottom = 0.0; - } - else { - yTop = 0.0; - yBottom = y; - } - break; - default: - break; } + break; + default: + break; + } - double yTransform = bounds.getHeight() - (yTopMargin + (yTop - yMin) / (yMax - yMin) * yTickSpace); + double yTransform = bounds.getHeight() - (yTopMargin + (yTop - yMin) / (yMax - yMin) * yTickSpace); - double yOffset = bounds.getY() + yTransform + 1; + double yOffset = bounds.getY() + yTransform; - double zeroTransform = bounds.getHeight() - (yTopMargin + (yBottom - yMin) / (yMax - yMin) * yTickSpace); - double zeroOffset = bounds.getY() + zeroTransform + 1; + double zeroTransform = bounds.getHeight() - (yTopMargin + (yBottom - yMin) / (yMax - yMin) * yTickSpace); + double zeroOffset = bounds.getY() + zeroTransform; - // paint bar - double barWidth = gridStep / getChartPainter().getAxisPair().getSeriesMap().size() / 1.1; - double barMargin = gridStep * .05; - double xOffset = bounds.getX() + xLeftMargin + gridStep * barCounter++ + seriesCounter * barWidth + barMargin; + // paint bar + boolean isOverlap = true; + double xOffset; + double barWidth; + if (getChartPainter().getStyleManager().barsOverlapped()) { + double barWidthPercentage = getChartPainter().getStyleManager().getBarWidthPercentage(); + barWidth = gridStep * barWidthPercentage; + double barMargin = gridStep * (1 - barWidthPercentage) / 2; + xOffset = bounds.getX() + xLeftMargin + gridStep * barCounter++ + barMargin; g.setColor(series.getStrokeColor()); - - Path2D.Double path = new Path2D.Double(); - path.moveTo(xOffset, yOffset); - path.lineTo(xOffset + barWidth, yOffset); - path.lineTo(xOffset + barWidth, zeroOffset); - path.lineTo(xOffset, zeroOffset); - path.closePath(); - g.fill(path); - } else { - barCounter++; + double barWidthPercentage = getChartPainter().getStyleManager().getBarWidthPercentage(); + barWidth = gridStep / getChartPainter().getAxisPair().getSeriesMap().size() * barWidthPercentage; + double barMargin = gridStep * (1 - barWidthPercentage) / 2; + xOffset = bounds.getX() + xLeftMargin + gridStep * barCounter++ + seriesCounter * barWidth + barMargin; + g.setColor(series.getStrokeColor()); } + Path2D.Double path = new Path2D.Double(); + path.moveTo(xOffset, yOffset); + path.lineTo(xOffset + barWidth, yOffset); + path.lineTo(xOffset + barWidth, zeroOffset); + path.lineTo(xOffset, zeroOffset); + path.closePath(); + g.fill(path); + } seriesCounter++; } diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentLineChart.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentLineChart.java index bf6ff687966e569c3ad820b67d79af463870ef35..d08eebb7f98c40cf9e027dbf14ff8f7e5d3ef69e 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentLineChart.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotContentLineChart.java @@ -55,12 +55,12 @@ public class PlotContentLineChart extends PlotContent { g.setClip(bounds); // X-Axis - int xTickSpace = (int)(styleManager.getAxisTickSpaceRatio() * bounds.getWidth()); - int xLeftMargin = Utils.getTickStartOffset((int) bounds.getWidth(), xTickSpace); + double xTickSpace = styleManager.getAxisTickSpaceRatio() * bounds.getWidth(); + double xLeftMargin = Utils.getTickStartOffset((int) bounds.getWidth(), xTickSpace); // Y-Axis - int yTickSpace = (int)(styleManager.getAxisTickSpaceRatio() * bounds.getHeight()); - int yTopMargin = Utils.getTickStartOffset((int) bounds.getHeight(), yTickSpace); + double yTickSpace = styleManager.getAxisTickSpaceRatio() * bounds.getHeight(); + double yTopMargin = Utils.getTickStartOffset((int) bounds.getHeight(), yTickSpace); for (Series series : getChartPainter().getAxisPair().getSeriesMap().values()) { @@ -103,7 +103,10 @@ public class PlotContentLineChart extends PlotContent { Iterator<?> xItr = xData.iterator(); Iterator<? extends Number> yItr = yData.iterator(); - + Iterator<? extends Number> ebItr = null; + if (errorBars != null) { + ebItr = errorBars.iterator(); + } Path2D.Double path = null; while (xItr.hasNext()) { @@ -182,7 +185,7 @@ public class PlotContentLineChart extends PlotContent { if (previousX != Integer.MIN_VALUE && previousY != Integer.MIN_VALUE) { g.setColor(series.getStrokeColor()); - double yBottomOfArea = bounds.getY() + bounds.getHeight() - yTopMargin + 1; + double yBottomOfArea = bounds.getY() + bounds.getHeight() - yTopMargin; if (path == null) { path = new Path2D.Double(); @@ -206,10 +209,7 @@ public class PlotContentLineChart extends PlotContent { } // paint errorbars - Iterator<? extends Number> ebItr = null; - if (errorBars != null) { - ebItr = errorBars.iterator(); - } + double eb = 0.0; if (errorBars != null) { @@ -256,6 +256,8 @@ public class PlotContentLineChart extends PlotContent { // close any open path for area charts closePath(g, path, previousX, bounds, yTopMargin); } + + g.setClip(null); } /** @@ -264,7 +266,7 @@ public class PlotContentLineChart extends PlotContent { private void closePath(Graphics2D g, Path2D.Double path, double previousX, Rectangle2D bounds, double yTopMargin) { if (path != null) { - double yBottomOfArea = bounds.getY() + bounds.getHeight() - yTopMargin + 1; + double yBottomOfArea = bounds.getY() + bounds.getHeight() - yTopMargin; path.lineTo(previousX, yBottomOfArea); path.closePath(); g.fill(path); diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotSurface.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotSurface.java index ed57666dff6b43f8337ce209b9126ebd637c9af7..5580d85bc1e92315a9276d4aa5901fd90e7a138a 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotSurface.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/PlotSurface.java @@ -67,7 +67,7 @@ public class PlotSurface implements ChartPart { if (getChartPainter().getStyleManager().isPlotGridLinesVisible() || getChartPainter().getStyleManager().isPlotTicksMarksVisible()) { // horizontal - List<Integer> yAxisTickLocations = getChartPainter().getAxisPair().getYAxis().getAxisTick().getTickLocations(); + List<Double> yAxisTickLocations = getChartPainter().getAxisPair().getYAxis().getAxisTick().getTickLocations(); for (int i = 0; i < yAxisTickLocations.size(); i++) { double tickLocation = yAxisTickLocations.get(i); @@ -95,9 +95,14 @@ public class PlotSurface implements ChartPart { // vertical if (getChartPainter().getStyleManager().getChartType() != ChartType.Bar - && (getChartPainter().getStyleManager().isPlotGridLinesVisible() || getChartPainter().getStyleManager().isPlotTicksMarksVisible())) { - List<Integer> xAxisTickLocations = getChartPainter().getAxisPair().getXAxis().getAxisTick().getTickLocations(); + && (getChartPainter().getStyleManager().isPlotGridLinesVisible() + + || getChartPainter().getStyleManager().isPlotTicksMarksVisible()) + + ) { + + List<Double> xAxisTickLocations = getChartPainter().getAxisPair().getXAxis().getAxisTick().getTickLocations(); for (int i = 0; i < xAxisTickLocations.size(); i++) { double tickLocation = xAxisTickLocations.get(i); diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/style/GGPlot2Theme.java b/xchart/src/main/java/com/xeiam/xchart/internal/style/GGPlot2Theme.java index 1d5ffdc4bc637d5a767137079513c531b99ce00e..1c99fe3c947592dff76fa03256eb6df37539dde2 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/style/GGPlot2Theme.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/style/GGPlot2Theme.java @@ -279,6 +279,20 @@ public class GGPlot2Theme implements Theme { return new BasicStroke(1.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); } + // Bar Charts /////////////////////////////// + + @Override + public double getBarWidthPercentage() { + + return 0.9; + } + + @Override + public boolean barsOverlapped() { + + return false; + } + // Error Bars /////////////////////////////// @Override diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/style/MatlabTheme.java b/xchart/src/main/java/com/xeiam/xchart/internal/style/MatlabTheme.java index 717ea8130a2d1114ffb795363c1ab7d461f3a08a..56bdc5f4d67ae4ea48c828cdbe0a9e739d01112f 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/style/MatlabTheme.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/style/MatlabTheme.java @@ -280,6 +280,20 @@ public class MatlabTheme implements Theme { } + // Bar Charts /////////////////////////////// + + @Override + public double getBarWidthPercentage() { + + return 0.9; + } + + @Override + public boolean barsOverlapped() { + + return false; + } + // Error Bars /////////////////////////////// @Override diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/style/Theme.java b/xchart/src/main/java/com/xeiam/xchart/internal/style/Theme.java index fe412c7330652984cebbee89d097a7b8ec064a15..e4e37fe3f6885ad0e51e133215a7e3d2ae801cea 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/style/Theme.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/style/Theme.java @@ -116,6 +116,12 @@ public interface Theme { public boolean isPlotTicksMarksVisible(); + // Bar Charts /////////////////////////////// + + public double getBarWidthPercentage(); + + public boolean barsOverlapped(); + // Error Bars /////////////////////////////// public Color getErrorBarsColor(); diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/style/XChartTheme.java b/xchart/src/main/java/com/xeiam/xchart/internal/style/XChartTheme.java index bfb470f18af9a56483b0f614ab7ab1448ce0f37e..1afe96c09037759ef769451113e8749a8f54c637 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/style/XChartTheme.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/style/XChartTheme.java @@ -279,6 +279,20 @@ public class XChartTheme implements Theme { return new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] { 3.0f, 3.0f }, 0.0f); } + // Bar Charts /////////////////////////////// + + @Override + public double getBarWidthPercentage() { + + return 0.9; + } + + @Override + public boolean barsOverlapped() { + + return false; + } + // Error Bars /////////////////////////////// @Override diff --git a/xchart/src/test/java/com/xeiam/xchart/DateAxisTickCalculatorTest.java b/xchart/src/test/java/com/xeiam/xchart/DateAxisTickCalculatorTest.java index 2c754beed7c56ffdc3115f3ddb70bf389a5a2768..051495ddeb63e2196ab2b5f512decb56da7df344 100644 --- a/xchart/src/test/java/com/xeiam/xchart/DateAxisTickCalculatorTest.java +++ b/xchart/src/test/java/com/xeiam/xchart/DateAxisTickCalculatorTest.java @@ -41,9 +41,9 @@ public class DateAxisTickCalculatorTest { assertThat(tickLabels.size(), equalTo(6)); assertThat(tickLabels.get(0), equalTo("17:50")); - List<Integer> tickLocations = decimalAxisTickCalculator.getTickLocations(); + List<Double> tickLocations = decimalAxisTickCalculator.getTickLocations(); System.out.println(Arrays.toString(tickLocations.toArray())); assertThat(tickLocations.size(), equalTo(6)); - assertThat(tickLocations.get(0), equalTo(100)); + assertThat(tickLocations.get(0), equalTo(100.0)); } } diff --git a/xchart/src/test/java/com/xeiam/xchart/DecimalAxisTickCalculatorTest.java b/xchart/src/test/java/com/xeiam/xchart/DecimalAxisTickCalculatorTest.java index ae145783141b9ed9141f6d235a4042a35ea9ca4a..e616b9e7cd0ce81cc99e9c1de8948d45cd7448d9 100644 --- a/xchart/src/test/java/com/xeiam/xchart/DecimalAxisTickCalculatorTest.java +++ b/xchart/src/test/java/com/xeiam/xchart/DecimalAxisTickCalculatorTest.java @@ -41,9 +41,9 @@ public class DecimalAxisTickCalculatorTest { assertThat(tickLabels.size(), equalTo(7)); assertThat(tickLabels.get(0), equalTo("-15")); - List<Integer> tickLocations = decimalAxisTickCalculator.getTickLocations(); + List<Double> tickLocations = decimalAxisTickCalculator.getTickLocations(); System.out.println(Arrays.toString(tickLocations.toArray())); assertThat(tickLocations.size(), equalTo(7)); - assertThat(tickLocations.get(0), equalTo(15)); + assertThat(tickLocations.get(0), equalTo(15.0)); } }