Skip to content

Commit 9e30cce

Browse files
added HistogramRenderer and HistogramRendererBarSample
1 parent 0c01f64 commit 9e30cce

File tree

11 files changed

+1579
-281
lines changed

11 files changed

+1579
-281
lines changed

chartfx-chart/src/main/java/de/gsi/chart/renderer/spi/HistogramRenderer.java

+377-185
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package de.gsi.chart.renderer.spi;
2+
3+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertFalse;
6+
import static org.junit.jupiter.api.Assertions.assertNotNull;
7+
import static org.junit.jupiter.api.Assertions.assertNull;
8+
import static org.junit.jupiter.api.Assertions.assertTrue;
9+
10+
import static de.gsi.chart.ui.utils.FuzzyTestImageUtils.compareAndWriteReference;
11+
import static de.gsi.chart.ui.utils.FuzzyTestImageUtils.writeTestImage;
12+
13+
import java.util.ArrayDeque;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
17+
import javafx.application.Platform;
18+
import javafx.scene.Scene;
19+
import javafx.scene.image.Image;
20+
import javafx.scene.layout.GridPane;
21+
import javafx.scene.layout.Priority;
22+
import javafx.stage.Stage;
23+
24+
import org.junit.jupiter.api.extension.ExtendWith;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
import org.testfx.framework.junit5.ApplicationExtension;
28+
import org.testfx.framework.junit5.Start;
29+
30+
import de.gsi.chart.Chart;
31+
import de.gsi.chart.XYChart;
32+
import de.gsi.chart.axes.spi.DefaultNumericAxis;
33+
import de.gsi.chart.renderer.LineStyle;
34+
import de.gsi.chart.ui.utils.JavaFXInterceptorUtils;
35+
import de.gsi.chart.ui.utils.TestFx;
36+
import de.gsi.chart.utils.FXUtils;
37+
import de.gsi.dataset.DataSet;
38+
import de.gsi.dataset.spi.AbstractErrorDataSet;
39+
import de.gsi.dataset.spi.TransposedDataSet;
40+
import de.gsi.dataset.testdata.spi.GaussFunction;
41+
import de.gsi.math.DataSetMath;
42+
import de.gsi.math.MathDataSet;
43+
44+
/**
45+
* Tests {@link de.gsi.chart.renderer.spi.HistogramRendererTests }
46+
*
47+
* @author rstein
48+
*
49+
*/
50+
@ExtendWith(ApplicationExtension.class)
51+
@ExtendWith(JavaFXInterceptorUtils.SelectiveJavaFxInterceptor.class)
52+
public class HistogramRendererTests {
53+
private static final Class<?> clazz = HistogramRendererTests.class;
54+
private static final Logger LOGGER = LoggerFactory.getLogger(clazz);
55+
private static final String className = clazz.getSimpleName();
56+
private static final String referenceFileName = "Reference_" + className;
57+
private static final String referenceFileExtension = ".png";
58+
private static final int MAX_TIMEOUT_MILLIS = 1000;
59+
private static final int WAIT_N_FX_PULSES = 3;
60+
private static final double IMAGE_CMP_THRESHOLD = 0.5; // 1.0 is perfect identity
61+
private static final int WIDTH = 1000;
62+
private static final int HEIGHT = 1000;
63+
private static final int N_SAMPLES = 15;
64+
private GridPane root;
65+
private Image testImage;
66+
67+
@Start
68+
public void start(Stage stage) {
69+
assertDoesNotThrow(HistogramRenderer::new);
70+
71+
root = new GridPane();
72+
// bar plots
73+
root.addRow(0, getChart(true, false, false, LineStyle.NONE, true), getChart(false, false, false, LineStyle.NONE, true), getChart(false, true, false, LineStyle.NONE, true));
74+
root.addRow(1, getChart(true, false, true, LineStyle.NONE, true), getChart(false, false, true, LineStyle.NONE, true), getChart(false, true, true, LineStyle.NONE, true));
75+
76+
// histogram plots
77+
root.addRow(2, getChart(true, false, false, LineStyle.BEZIER_CURVE, false), getChart(true, true, false, LineStyle.HISTOGRAM, false), getChart(false, true, false, LineStyle.HISTOGRAM_FILLED, false));
78+
root.addRow(3, getChart(true, false, true, LineStyle.BEZIER_CURVE, false), getChart(true, true, true, LineStyle.HISTOGRAM, false), getChart(false, true, true, LineStyle.HISTOGRAM_FILLED, false));
79+
80+
stage.setScene(new Scene(root, WIDTH, HEIGHT));
81+
stage.setTitle(getClass().getSimpleName());
82+
stage.setOnCloseRequest(evt -> Platform.exit());
83+
stage.show();
84+
}
85+
86+
@TestFx
87+
void basicInterfaceTests() {
88+
final HistogramRenderer renderer = new HistogramRenderer();
89+
90+
assertFalse(renderer.isAnimate());
91+
assertNotNull(renderer.animateProperty());
92+
assertDoesNotThrow(() -> renderer.setAnimate(true));
93+
assertTrue(renderer.isAnimate());
94+
95+
assertTrue(renderer.isAutoSorting());
96+
assertNotNull(renderer.autoSortingProperty());
97+
assertDoesNotThrow(() -> renderer.setAutoSorting(false));
98+
assertFalse(renderer.isAutoSorting());
99+
100+
final XYChart chart = new XYChart();
101+
assertNotNull(renderer.chartProperty());
102+
assertNull(renderer.getChart());
103+
assertDoesNotThrow(renderer::requestLayout);
104+
assertDoesNotThrow(() -> renderer.setChartChart(chart));
105+
assertEquals(chart, renderer.getChart());
106+
assertDoesNotThrow(renderer::requestLayout);
107+
108+
assertTrue(renderer.isRoundedCorner());
109+
assertNotNull(renderer.roundedCornerProperty());
110+
assertDoesNotThrow(() -> renderer.setRoundedCorner(false));
111+
assertFalse(renderer.isRoundedCorner());
112+
113+
assertEquals(10, renderer.getRoundedCornerRadius());
114+
assertNotNull(renderer.roundedCornerRadiusProperty());
115+
assertDoesNotThrow(() -> renderer.setRoundedCornerRadius(20));
116+
assertEquals(20, renderer.getRoundedCornerRadius());
117+
}
118+
119+
public static Chart getChart(final boolean shifted, final boolean stacked, final boolean vertical, final LineStyle lineStyle, final boolean drawBars) {
120+
final HistogramRenderer renderer = new HistogramRenderer();
121+
renderer.setDrawBars(drawBars);
122+
renderer.setPolyLineStyle(lineStyle);
123+
if (drawBars) {
124+
renderer.setPolyLineStyle(LineStyle.NONE);
125+
} else {
126+
renderer.setDrawBars(false);
127+
}
128+
renderer.setShiftBar(shifted);
129+
130+
renderer.getDatasets().setAll(getTestDataSets(stacked, vertical));
131+
if (vertical) {
132+
renderer.setAutoSorting(false); // N.B. for the time being, auto-sorting needs to be disabled for vertical datasets....
133+
}
134+
135+
final DefaultNumericAxis xAxis = new DefaultNumericAxis("abscissa", null);
136+
final DefaultNumericAxis yAxis = new DefaultNumericAxis("ordinate" + (stacked ? " (stacked)" : ""), null);
137+
yAxis.setAutoRangeRounding(false);
138+
yAxis.setAutoRangePadding(0.3);
139+
140+
final XYChart chart;
141+
chart = new XYChart(vertical ? yAxis : xAxis, vertical ? xAxis : yAxis);
142+
chart.getRenderers().set(0, renderer);
143+
chart.legendVisibleProperty().set(true);
144+
chart.setLegendVisible(false);
145+
GridPane.setHgrow(chart, Priority.ALWAYS);
146+
GridPane.setVgrow(chart, Priority.ALWAYS);
147+
148+
return chart;
149+
}
150+
151+
private String getReferenceImageFileName() {
152+
final String options = "_default";
153+
return referenceFileName + options + referenceFileExtension;
154+
}
155+
156+
@TestFx
157+
void testRenderer() throws Exception {
158+
final String referenceImage = getReferenceImageFileName();
159+
FXUtils.runAndWait(() -> testImage = root.snapshot(null, null));
160+
final double tresholdIdentity = compareAndWriteReference(clazz, referenceImage, testImage);
161+
if (IMAGE_CMP_THRESHOLD < tresholdIdentity) {
162+
if (LOGGER.isTraceEnabled()) {
163+
LOGGER.atInfo().addArgument(tresholdIdentity).log("image identity - threshold = {}");
164+
}
165+
} else {
166+
// write image to report repository
167+
writeTestImage(clazz, "Test_" + clazz.getSimpleName() + "_identity.png", testImage);
168+
if (LOGGER.isTraceEnabled()) {
169+
LOGGER.atWarn().addArgument(tresholdIdentity).log("image identity - threshold exceeded = {}");
170+
}
171+
}
172+
173+
FXUtils.runAndWait(() -> root.requestLayout());
174+
assertTrue(FXUtils.waitForFxTicks(root.getScene(), WAIT_N_FX_PULSES, MAX_TIMEOUT_MILLIS));
175+
176+
FXUtils.runAndWait(() -> testImage = root.snapshot(null, null));
177+
final double tresholdNonIdentity = compareAndWriteReference(clazz, referenceImage, testImage);
178+
if (IMAGE_CMP_THRESHOLD > tresholdNonIdentity) {
179+
if (LOGGER.isTraceEnabled()) {
180+
LOGGER.atInfo().addArgument(tresholdNonIdentity).log("image non-identity - threshold = {}");
181+
}
182+
} else {
183+
// write image to report repository
184+
writeTestImage(clazz, "Test_" + clazz.getSimpleName() + "_nonidentity.png", testImage);
185+
if (LOGGER.isTraceEnabled()) {
186+
LOGGER.atWarn().addArgument(tresholdNonIdentity).log("image non-identity - threshold exceeded = {}");
187+
}
188+
}
189+
}
190+
191+
private static List<DataSet> getTestDataSets(final boolean stacked, final boolean transposed) {
192+
final List<DataSet> dataSets = new ArrayList<>();
193+
for (int centre : new int[] { 2 * N_SAMPLES / 5, N_SAMPLES / 3, 2 * N_SAMPLES / 3 }) {
194+
final AbstractErrorDataSet<?> gauss = new GaussFunction("h" + centre, N_SAMPLES, centre, 0.1 * N_SAMPLES);
195+
gauss.addDataLabel(centre, "special point for " + gauss.getName());
196+
gauss.addDataStyle(centre, "strokeColor=cyan; fillColor=cyan; markerColor=cyan;");
197+
dataSets.add(gauss);
198+
}
199+
if (stacked) {
200+
final SummingDataSet sum123 = new SummingDataSet("Sum", new SummingDataSet("Sum", dataSets.toArray(new DataSet[0])));
201+
final SummingDataSet sum12 = new SummingDataSet("Sum", new SummingDataSet("Sum", dataSets.subList(0, 1).toArray(new DataSet[0])));
202+
dataSets.set(0, sum123);
203+
dataSets.set(1, sum12);
204+
}
205+
206+
if (transposed) {
207+
dataSets.set(0, TransposedDataSet.transpose(dataSets.get(0)));
208+
dataSets.set(1, TransposedDataSet.transpose(dataSets.get(1)));
209+
dataSets.set(2, TransposedDataSet.transpose(dataSets.get(2)));
210+
}
211+
212+
return dataSets;
213+
}
214+
215+
public static class SummingDataSet extends MathDataSet { // NOSONAR NOPMD -- too many parents is out of our control (Java intrinsic)
216+
public SummingDataSet(final String name, final DataSet... functions) {
217+
super(name, (dataSets, returnFunction) -> {
218+
if (dataSets.isEmpty()) {
219+
return;
220+
}
221+
final ArrayDeque<DataSet> lockQueue = new ArrayDeque<>(dataSets.size());
222+
try {
223+
dataSets.forEach(ds -> {
224+
lockQueue.push(ds);
225+
ds.lock().readLock();
226+
});
227+
returnFunction.clearData();
228+
final DataSet firstDataSet = dataSets.get(0);
229+
returnFunction.add(firstDataSet.get(DIM_X, 0), 0);
230+
returnFunction.add(firstDataSet.get(DIM_X, firstDataSet.getDataCount() - 1), 0);
231+
dataSets.forEach(ds -> returnFunction.set(DataSetMath.addFunction(returnFunction, ds), false));
232+
} finally {
233+
// unlock in reverse order
234+
while (!lockQueue.isEmpty()) {
235+
lockQueue.pop().lock().readUnLock();
236+
}
237+
}
238+
}, functions);
239+
}
240+
}
241+
}

chartfx-dataset/src/main/java/de/gsi/dataset/Histogram.java

+6
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ public interface Histogram extends DataSet, DataSetMetaData {
5757
*/
5858
double getBinCenter(final int dimIndex, final int binIndex);
5959

60+
/**
61+
* @param dimIndex the dimension index
62+
* @return the number of bins for the given dimIndex (includes the under- and over-flow bin)
63+
*/
64+
int getBinCount(final int dimIndex);
65+
6066
/**
6167
*
6268
* @param dimIndex the dimension index

0 commit comments

Comments
 (0)