Skip to content

Commit

Permalink
Merge pull request #422 from Mr14huashao/issue_410
Browse files Browse the repository at this point in the history
[resolves #410] Box plot feature fixes and code optimizations
  • Loading branch information
timmolter committed Feb 28, 2020
2 parents 8fec618 + 8b5911f commit 0973bc5
Show file tree
Hide file tree
Showing 15 changed files with 485 additions and 404 deletions.
72 changes: 39 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,44 +218,50 @@ Series render styles include: `Candle`, `HiLo`.

![](https://raw.githubusercontent.com/knowm/XChart/develop/etc/XChart_BoxPlot.png)

`BoxPlot` charts take Date, Number or String data types for the X-Axis and Number data types for the Y-Axis. Each box chart is calculated from the corresponding series.
`BoxPlot` charts take String data (seriesNames) types for the X-Axis and Number data types for the Y-Axis. Each box chart is calculated from the corresponding series yData.
Create a BoxPlot via a BoxChartBuilder, style chart, add a series to it
```java
// Create Chart
BoxChart chart = new BoxChartBuilder().title("box plot demo").xAxisTitle("Color").yAxisTitle("temperature").theme(ChartTheme.GGPlot2).build();
// Customize Chart
chart.getStyler().setShowWithinAreaPoint(true);
BoxChart chart =
new BoxChartBuilder().title("box plot demo").build();

// Choose a calculation method
chart.getStyler().setBoxplotCalCulationMethod(BoxplotCalCulationMethod.N_LESS_1_PLUS_1);
chart.getStyler().setToolTipsEnabled(true);

// Series
chart.addSeries("seriesName", Arrays.asList(40, -30, 20, 60, 50));
new SwingWrapper(chart).displayChart();
chart.addSeries("boxOne", Arrays.asList(1,2,3,4));
new SwingWrapper<BoxChart>(chart).displayChart();
```
The BoxPlot calculation method is as follows:
1, If the calculation results are integers, the corresponding position is taken
For example, say the y value are y<sub>1</sub> = 1, y<sub>2</sub> = 2, y<sub>3</sub> = 3. And n = 3;
The first quartile position should be q1 = (n + 1) /4
The second quartile position should be q2 = 2 * (n + 1) /4
The third quartile position should be q3 = 3 * (n + 1) /4
In this way we can calculate the positions:
q1 = (3 + 1) / 4 = 1
q2 = 2 * (3 + 1) / 4 = 2
q3 = 3 * (3 + 1) / 4 = 3
Q1 = y<sub>q1</sub> = 1, Q2 = y<sub>q2</sub> = 2, Q3 = y<sub>q3</sub>
Then the first quartile Q1 = 1, the second quartile Q2 = 2, and the third quartile Q3 = 3

2, If the calculation results are NOT integers, the positions should be calculated with the following formula
For another example, say the y value are y<sub>1</sub> = 1, y<sub>2</sub> = 2, y<sub>3</sub> = 3, y<sub>4</sub> = 4. And n = 4;
q1 = (n + 1) / 4 = (4 + 1) / 4 = 1.25
q2 = 2 * (n + 1) / 4 = 2 * (4 + 1) / 4 = 2.5
q3 = 3 * (n + 1) / 4 = 3 * (4 + 1) / 4 = 3.75
As we can see, the calculated values are NOT integers. In this case we shold calculate in the following way:
Q = y<sub>(int)q</sub> + (y<sub>(int)q + 1</sub> - y<sub>(int)q</sub>) * (q % 1)
so
the first quartile: Q1 = y<sub>(int)q1</sub> + (y<sub>(int)q1 + 1</sub> - y<sub>(int)q1</sub>) * (q1 % 1) = 1 + (2 - 1) * 0.25 = 1.25
the second quartile: Q2 = y<sub>(int)q2</sub> + (y<sub>(int)q2 + 1</sub> - y<sub>(int)q2</sub>) * (q2 % 1) = 2 + (3 - 2) * 0.5 = 2.5
the third quartile: Q3 = y<sub>(int)q3</sub> + (y<sub>(int)q3 + 1</sub> - y<sub>(int)q3</sub>) * (q3 % 1) = 3 + (4 - 3) * 0.75 = 3.75

Upper limit = Q3 + 1.5 * IQR = Q3 + 1.5 * (Q3 - Q1)
lower limit = Q3 - 1.5 * IQR = Q3 - 1.5 * (Q3 - Q1)
Four calculation methods for boxplots:
- "N_PLUS_1": determine the position of the quartile, where Qi is = i (n + 1) / 4, where i = 1, 2, and 3. n represents the number of items contained in the sequence.
Calculate the corresponding quartile based on location
- "N_LESS_1": Determine the position of the quartile, where Qi is = i (n-1) / 4, where i = 1, 2, and 3. n represents the number of items contained in the sequence.
Calculate the corresponding quartile based on location
- "NP": Determine the position of the quartile, where Qi is np = (i * n) / 4, where i = 1, 2, and 3. n represents the number of items contained in the sequence.
If np is not an integer, Qi = X [np + 1]
If np is an integer, Qi = (X [np] + X [np + 1]) / 2
- "N_LESS_1_PLUS_1": Determine the position of the quartile, where Qi is = i (n-1) / 4 + 1, where i = 1, 2, 3. n represents the number of items contained in the sequence.
Calculate the corresponding quartile based on location

Interquartile range, IQR = Q3-Q1
Upper whisker = Q3 + 1.5 * IQR = Q3 + 1.5 * (Q3 - Q1), if Upper whisker is greater than the maximum value of yData, Upper whisker = maximum value of yData.
Lower whisker = Q1 - 1.5 * IQR = Q1 - 1.5 * (Q3 -Q1), if the lower whisker is less than the minimum value of yData, the lower whisker = the minimum value of yData.

Example:
An example of a set of sequence numbers: 12, 15, 17, 19, 20, 23, 25, 28, 30, 33, 34, 35, 36, 37
- "N_PLUS_1":
Q1's position = (14 + 1) /4=3.75,
Q1 = 0.25 × third term + 0.75 × fourth term = 0.25 × 17 + 0.75 × 19 = 18.5;
- "N_LESS_1":
Q1's location = (14-1) /4=3.25,
Q1 = 0.75 × third term + 0.25 × fourth term = 0.75 × 17 + 0.25 × 19 = 17.5;
- "NP":
Q1's position = 14 * 0.25 = 3.5,
Q1 = 19;
- "N_LESS_1_PLUS_1":
Q1's location = (14-1) / 4 + 1 = 4.25
Q1 = 0.75 × the fourth term + 0.25 × the fifth term = 0.75 × 19 + 0.25 × 20 = 19.25.

## Real-time Java Charts using XChart

Expand Down
Binary file modified etc/XChart_BoxPlot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.Arrays;
import org.knowm.xchart.BoxChart;
import org.knowm.xchart.BoxChartBuilder;
import org.knowm.xchart.BoxSeries;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.demo.charts.ExampleChart;
import org.knowm.xchart.style.Styler.ChartTheme;
Expand All @@ -26,15 +25,15 @@ public BoxChart getChart() {
BoxChart chart =
new BoxChartBuilder()
.title("box plot demo")
.xAxisTitle("Color")
.yAxisTitle("temperature")
.xAxisTitle("X")
.yAxisTitle("Y")
.theme(ChartTheme.GGPlot2)
.build();

// Series
BoxSeries[] boxSeries = new BoxSeries[3];
boxSeries[0] = chart.addSeries("aaa", Arrays.asList(40, 30, 20, 60, 50));
boxSeries[1] = chart.addSeries("bbb", Arrays.asList(-20, -10, -30, -15, -25));
boxSeries[2] = chart.addSeries("ccc", Arrays.asList(50, -20));
chart.addSeries("aaa", Arrays.asList(40, 30, 20, 60, 50));
chart.addSeries("bbb", Arrays.asList(-20, -10, -30, -15, -25));
chart.addSeries("ccc", Arrays.asList(50, -20));
return chart;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.knowm.xchart.BoxChartBuilder;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.demo.charts.ExampleChart;
import org.knowm.xchart.style.BoxPlotStyler.BoxplotCalCulationMethod;
import org.knowm.xchart.style.Styler.ChartTheme;

/*
Expand All @@ -26,20 +27,18 @@ public BoxChart getChart() {
BoxChart chart =
new BoxChartBuilder()
.title("box plot show all point")
.xAxisTitle("Color")
.yAxisTitle("temperature")
.xAxisTitle("X")
.yAxisTitle("Y")
.theme(ChartTheme.Matlab)
.build();
// Series

chart.addSeries(
"aaa",
Arrays.asList(
9634.37, 23886.43, 13828.96, 7773.08, 14959.32, 8046.95, 6547.51, 9528.85, 9241.53,
9353.79, 8224.60, 10436.48, 10399.62, 15067.39, 8505.73, 9398.87, 11611.29, 12280.94,
9631.96));
chart.addSeries("bbb", Arrays.asList(7000, 8000, 9000));
chart.addSeries("ccc", Arrays.asList(7000, 8000, null, 9000));
// Customize Chart
chart.getStyler().setBoxplotCalCulationMethod(BoxplotCalCulationMethod.N_LESS_1_PLUS_1);

// Series
chart.addSeries("aaa", Arrays.asList(1, 2, 3, 4, 5, 6));
chart.addSeries("bbb", Arrays.asList(1, 2, 3, 4, 5, 6, 17));
chart.addSeries("ccc", Arrays.asList(-10, -8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 21));
chart.getStyler().setShowWithinAreaPoint(true);
chart.getStyler().setToolTipsEnabled(true);
return chart;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import java.util.Arrays;
import org.knowm.xchart.BoxChart;
import org.knowm.xchart.BoxChartBuilder;
import org.knowm.xchart.BoxSeries;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.demo.charts.ExampleChart;
import org.knowm.xchart.style.Styler.ChartTheme;

/*
* Box Plot with 6 series
* Box Plot with 1 series
* and show ToolTips
* and Y-Axis is logarithmic
*/
Expand All @@ -30,23 +29,17 @@ public BoxChart getChart() {
.width(600)
.height(450)
.title("Y Axis Logarithmic-box plot demo")
.xAxisTitle("Color")
.yAxisTitle("temperature")
.xAxisTitle("X")
.yAxisTitle("Y")
.theme(ChartTheme.XChart)
.build();
// Series
BoxSeries[] boxSeries = new BoxSeries[6];
boxSeries[0] = chart.addSeries("aaa", Arrays.asList(40, 30, 20, 60, 50));
boxSeries[1] = chart.addSeries("bbb", Arrays.asList(20, 10, 25, null, 60));
boxSeries[2] = chart.addSeries("ccc", Arrays.asList(30, 20, 22, 40, 50));
boxSeries[3] = chart.addSeries("ddd", Arrays.asList(10, 15, 28, 50, 65));
boxSeries[4] = chart.addSeries("eee", Arrays.asList(25, 25, 30, 30, 55));
boxSeries[5] = chart.addSeries("fff", Arrays.asList(15, 18, 29, 20, 50));

// Customize Chart
chart.getStyler().setToolTipsEnabled(true);
chart.getStyler().setYAxisLogarithmic(true);
chart.getStyler().setYAxisMin(1d);
chart.getStyler().setYAxisMax(160d);

// Series
chart.addSeries("aaa", Arrays.asList(10, 40, 80, 120, 350));
return chart;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.knowm.xchart.standalone.issues;

import java.util.Arrays;

import org.knowm.xchart.BoxChart;
import org.knowm.xchart.BoxChartBuilder;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.style.Styler.ChartTheme;

public class TestForIssue410 {

public static void main(String[] args) {

// Create Chart
BoxChart chart =
new BoxChartBuilder().title("TestForIssue410").theme(ChartTheme.GGPlot2).build();

chart.getStyler().setToolTipsEnabled(true);

// Series
chart.addSeries("boxOne", Arrays.asList(1000, 5000, 60000));
new SwingWrapper<BoxChart>(chart).displayChart();
}
}
47 changes: 22 additions & 25 deletions xchart/src/main/java/org/knowm/xchart/BoxChart.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.knowm.xchart.internal.Utils;
import org.knowm.xchart.internal.chartpart.AxisPair;
import org.knowm.xchart.internal.chartpart.Chart;
Expand All @@ -19,8 +20,7 @@

public class BoxChart extends Chart<BoxPlotStyler, BoxSeries> {

private ArrayList xData = new ArrayList();
private ArrayList newXData = new ArrayList();
private List<String> xData = new ArrayList<>();

protected BoxChart(int width, int height) {

Expand All @@ -35,6 +35,8 @@ public BoxChart(int width, int height, Theme theme) {

this(width, height);
styler.setTheme(theme);
// Box chart Legend does not show
styler.setLegendVisible(false);
}

public BoxChart(int width, int height, ChartTheme chartTheme) {
Expand Down Expand Up @@ -77,48 +79,43 @@ private void sanityCheck(String seriesName, List<? extends Number> yData) {
+ seriesName
+ " < has already been used. Use unique names for each series!!!");
}

sanityCheckYData(yData);
}

private void sanityCheckYData(List<? extends Number> yData) {

if (yData == null) {
throw new IllegalArgumentException("Y-Axis data connot be null !!!");
}
if (yData.size() == 0) {
throw new IllegalArgumentException("Y-Axis data connot be empyt !!!");
}
if (yData.contains(null)) {
throw new IllegalArgumentException("Y-Axis data cannot contain null !!!");
}
}

public BoxSeries updateBoxSeries(String seriesName, int[] newYData) {

return updateBoxSeries(seriesName, Utils.getNumberListFromIntArray(newYData));
}

public BoxSeries updateBoxSeries(String seriesName, double[] newYData) {

newXData.add(seriesName);
return updateBoxSeries(
seriesName,
newXData,
Utils.getNumberListFromDoubleArray(newYData),
Utils.getNumberListFromDoubleArray(null));
return updateBoxSeries(seriesName, Utils.getNumberListFromDoubleArray(newYData));
}

public BoxSeries updateBoxSeries(
String seriesName,
List<?> newXData,
List<? extends Number> newYData,
List<? extends Number> newErrorBarData) {
public BoxSeries updateBoxSeries(String seriesName, List<? extends Number> newYData) {

Map<String, BoxSeries> seriesMap = getSeriesMap();
BoxSeries series = seriesMap.get(seriesName);

if (series == null) {
throw new IllegalArgumentException("Series name > " + seriesName + " < not found !!!");
}

if (newXData == null) {
// generate X-Data
List<Integer> generatedXData = new ArrayList<Integer>();

for (int i = 1; i <= newYData.size(); i++) {
generatedXData.add(i);
}
series.replaceData(generatedXData, newYData, newErrorBarData);
} else {
series.replaceData(newXData, newYData, newErrorBarData);
}
sanityCheckYData(newYData);
series.replaceData(newYData);
return series;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.knowm.xchart.CategorySeries.CategorySeriesRenderStyle;
import org.knowm.xchart.internal.series.AxesChartSeries;
import org.knowm.xchart.internal.series.AxesChartSeriesCategory;
Expand Down Expand Up @@ -464,53 +465,19 @@ private void overrideMinMaxForYAxis(Axis yAxis) {
overrideYAxisMaxValue = 0.0;
}
}
} else if (chart.getStyler() instanceof BoxPlotStyler) {
// the maximum value of the box plot may be greater than all y values, and the
// minimum value of the box plot may be less than all y values

Map<String, S> seriesMap = chart.getSeriesMap();
ST boxPlotStyler = chart.getStyler();
BoxChartData<ST, S> boxChartData = new BoxChartData<>();
int numBoxPlot = seriesMap.size();
Double boxPlotYData[][] = new Double[numBoxPlot][BoxChartData.BOX_DATAS_LENGTH];
boxPlotYData = boxChartData.getBoxPlotData(seriesMap, boxPlotStyler);

for (int noNumBox = 0; noNumBox < numBoxPlot; noNumBox++) {

if (boxPlotYData[noNumBox][BoxChartData.MIN_BOX_VALUE_INDEX] != null
&& !boxPlotStyler.isYAxisLogarithmic()
&& overrideYAxisMinValue > boxPlotYData[noNumBox][BoxChartData.MIN_BOX_VALUE_INDEX]) {
overrideYAxisMinValue = boxPlotYData[noNumBox][BoxChartData.MIN_BOX_VALUE_INDEX];
} else if (boxPlotYData[noNumBox][BoxChartData.MIN_BOX_VALUE_INDEX] != null
&& boxPlotStyler.isYAxisLogarithmic()
&& overrideYAxisMinValue
> Math.pow(10, boxPlotYData[noNumBox][BoxChartData.MIN_BOX_VALUE_INDEX])) {
overrideYAxisMinValue =
Math.pow(10, boxPlotYData[noNumBox][BoxChartData.MIN_BOX_VALUE_INDEX]);
}
if (boxPlotYData[noNumBox][BoxChartData.MAX_BOX_VALUE_INDEX] != null
&& !boxPlotStyler.isYAxisLogarithmic()
&& overrideYAxisMaxValue < boxPlotYData[noNumBox][BoxChartData.MAX_BOX_VALUE_INDEX]) {
overrideYAxisMaxValue = boxPlotYData[noNumBox][BoxChartData.MAX_BOX_VALUE_INDEX];
} else if (boxPlotYData[noNumBox][BoxChartData.MAX_BOX_VALUE_INDEX] != null
&& boxPlotStyler.isYAxisLogarithmic()
&& overrideYAxisMaxValue
< Math.pow(10, boxPlotYData[noNumBox][BoxChartData.MAX_BOX_VALUE_INDEX])) {
overrideYAxisMaxValue =
Math.pow(10, boxPlotYData[noNumBox][BoxChartData.MAX_BOX_VALUE_INDEX]);
}
}
}

// override min and maxValue if specified
if (chart.getStyler().getYAxisMin(yAxis.getYIndex()) != null) {
if (chart.getStyler().getYAxisMin(yAxis.getYIndex()) != null
&& !(chart.getStyler() instanceof BoxPlotStyler)) {
overrideYAxisMinValue = chart.getStyler().getYAxisMin(yAxis.getYIndex());
} else if (chart.getStyler().getYAxisMin() != null
&& !(chart.getStyler() instanceof BoxPlotStyler)) {
overrideYAxisMinValue = chart.getStyler().getYAxisMin();
}

if (chart.getStyler().getYAxisMax(yAxis.getYIndex()) != null) {
if (chart.getStyler().getYAxisMax(yAxis.getYIndex()) != null
&& !(chart.getStyler() instanceof BoxPlotStyler)) {
overrideYAxisMaxValue = chart.getStyler().getYAxisMax(yAxis.getYIndex());
} else if (chart.getStyler().getYAxisMax() != null
&& !(chart.getStyler() instanceof BoxPlotStyler)) {
Expand Down
Loading

0 comments on commit 0973bc5

Please sign in to comment.