JavaFX Linechart with logarithmic axis not updating well [closed] - java

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I have two linecharts in my application. One is with linear axis and the other is with logarithmic axis.
When I want so view only one series in the charts I set the other series and their data not visible so i can see only that series and i use the same method for visualize again all series too.
I've tried with threads but my problem is still there: in the chart with linear axis I don't have any problem but the logarithmic one doesn't update well the data.
Some nodes remain or are not shown, for example, it seems like the chart freeze while adding or removing visibility of data. Everything goes well only if I do a resize of the window and I don't understand why it is correlated. Here is my method to show only series with a certain name:
new Thread(() -> {
for (Series<Number, Number> series : lineChart.getData()) {
Platform.runLater(() -> {
if (series.getName().equals(name)) {
series.getNode().setVisible(!series.getNode().isVisible());
series.getData().forEach(data -> data.getNode().setVisible(series.getNode().isVisible()));
}
});
}
}).start();
Here is the class i use for the logarithmic axis:
public class LogarithmicAxis extends ValueAxis<Number> {
private Object currentAnimationID;
private final ChartLayoutAnimator animator = new ChartLayoutAnimator(this);
private final DoubleProperty logUpperBound = new SimpleDoubleProperty();
private final DoubleProperty logLowerBound = new SimpleDoubleProperty();
public LogarithmicAxis() {
super(0.0001, 1000);
bindLogBoundsToDefaultBounds();
}
public LogarithmicAxis(double lowerBound, double upperBound) {
super(lowerBound, upperBound);
validateBounds(lowerBound, upperBound);
bindLogBoundsToDefaultBounds();
}
public void setLogarithmicUpperBound(double d) {
double nd = Math.pow(10, Math.ceil(Math.log10(d)));
setUpperBound(nd == d ? nd * 10 : nd);
}
/**
* Binds logarithmic bounds with the super class bounds, consider the
* base 10 logarithmic scale.
*/
private void bindLogBoundsToDefaultBounds() {
logLowerBound.bind(new DoubleBinding() {
{
super.bind(lowerBoundProperty());
}
#Override
protected double computeValue() {
return Math.log10(lowerBoundProperty().get());
}
});
logUpperBound.bind(new DoubleBinding() {
{
super.bind(upperBoundProperty());
}
#Override
protected double computeValue() {
return Math.log10(upperBoundProperty().get());
}
});
}
/**
* Validates the bounds by throwing an exception if the values are not
* conform to the mathematics log interval: [0,Double.MAX_VALUE]
*
*/
private void validateBounds(double lowerBound, double upperBound) throws IllegalLogarithmicRangeException {
if (lowerBound < 0 || upperBound < 0 || lowerBound > upperBound) {
throw new IllegalLogarithmicRangeException(
"The logarithmic range should be in [0,Double.MAX_VALUE] and the lowerBound should be less than the upperBound");
}
}
/**
* It is used to get the list of minor tick marks position to display on the axis.
* It's based on the number of minor tick and the logarithmic formula.
*
*/
#Override
protected List<Number> calculateMinorTickMarks() {
List<Number> minorTickMarksPositions = new ArrayList<>();
return minorTickMarksPositions;
}
//Then, the calculateTickValues method
/**
* It is used to calculate a list of all the data values for each tick mark in range,
* represented by the second parameter. Displays one tick each power of 10.
*
*/
#Override
protected List<Number> calculateTickValues(double length, Object range) {
LinkedList<Number> tickPositions = new LinkedList<>();
if (range != null) {
double lowerBound = ((double[]) range)[0];
double upperBound = ((double[]) range)[1];
for (double i = Math.log10(lowerBound); i <= Math.log10(upperBound); i++) {
tickPositions.add(Math.pow(10, i));
}
if (!tickPositions.isEmpty()) {
if (tickPositions.getLast().doubleValue() != upperBound) {
tickPositions.add(upperBound);
}
}
}
return tickPositions;
}
/**
* The getRange provides the current range of the axis. A basic
* implementation is to return an array of the lowerBound and upperBound
* properties defined into the ValueAxis class.
*
*/
#Override
protected double[] getRange() {
return new double[]{
getLowerBound(),
getUpperBound()
};
}
/**
* The getTickMarkLabel is only used to convert the number value to a string
* that will be displayed under the tickMark.
*
*/
#Override
protected String getTickMarkLabel(Number value) {
NumberFormat formatter = NumberFormat.getInstance();
formatter.setMaximumIntegerDigits(10);
formatter.setMinimumIntegerDigits(1);
return formatter.format(value);
}
/**
* Updates the range when data are added into the chart.
* There is two possibilities, the axis is animated or not. The
* simplest case is to set the lower and upper bound properties directly
* with the new values.
*
*/
#Override
protected void setRange(Object range, boolean animate) {
if (range != null) {
final double[] rangeProps = (double[]) range;
final double lowerBound = rangeProps[0];
final double upperBound = rangeProps[1];
final double oldLowerBound = getLowerBound();
setLowerBound(lowerBound);
setUpperBound(upperBound);
if (animate) {
animator.stop(currentAnimationID);
currentAnimationID = animator.animate(
new KeyFrame(Duration.ZERO,
new KeyValue(currentLowerBound, oldLowerBound)
),
new KeyFrame(Duration.millis(700),
new KeyValue(currentLowerBound, lowerBound)
)
);
} else {
currentLowerBound.set(lowerBound);
}
}
}
#Override
public Number getValueForDisplay(double displayPosition) {
double delta = logUpperBound.get() - logLowerBound.get();
if (getSide().isVertical()) {
return Math.pow(10, (((displayPosition - getHeight()) / -getHeight()) * delta) + logLowerBound.get());
} else {
return Math.pow(10, (((displayPosition / getWidth()) * delta) + logLowerBound.get()));
}
}
#Override
public double getDisplayPosition(Number value) {
double delta = logUpperBound.get() - logLowerBound.get();
double deltaV = Math.log10(value.doubleValue()) - logLowerBound.get();
if (getSide().isVertical()) {
return (1. - ((deltaV) / delta)) * getHeight();
} else {
return ((deltaV) / delta) * getWidth();
}
}
/**
* Exception to be thrown when a bound value isn't supported by the
* logarithmic axis<br>
*
*/
public static class IllegalLogarithmicRangeException extends RuntimeException {
public IllegalLogarithmicRangeException(String message) {
super(message);
}
}
}

view only one series in the charts
One way to do this is to place one series in the chart and remove all others.
To do that, don't try to hide the nodes in other series, but, instead, remove other series from the data set, the charts will update automatically.
Turn off animation on the chart if you want an instant update instead of an animated one.
lineChart.setAnimated(false);
I've tried with threads
I don't advise using other threads unless you really need to.
Never access data associated with the active scene graph off of the JavaFX thread. This would include the line graph and its data.
To allow that to happen, run the logic which gets the lineChart data within the Platform.runlater() call, rather than accessing the lineChart.getData() call in your own thread as you have in your question.
new Thread(() -> {
Platform.runLater(() -> {
for (Series<Number, Number> series : lineChart.getData()) {
if (series.getName().equals(name)) {
series.getNode().setVisible(!series.getNode().isVisible());
series.getData().forEach(data -> data.getNode().setVisible(series.getNode().isVisible()));
}
}
});
}).start();
But then, if you do that, the thread and runLater calls seem pointless because you could just do everything inline:
for (Series<Number, Number> series : lineChart.getData()) {
if (series.getName().equals(name)) {
series.getNode().setVisible(!series.getNode().isVisible());
series.getData().forEach(data -> data.getNode().setVisible(series.getNode().isVisible()));
}
}
Example Code
I tried implementing a kind of "only include displayed series" strategy.
After implementing, I think that perhaps your original idea of hiding series by changing their visibility might be better.
It was a little bit trickier than I expected because when you only display a single series, it is colored by the default coloring scheme, where the colors are assigned by the sequential position of the series in the data. So, when you only display one series instead of many, the color of the series displayed actually changes, unless you override the default color scheme.
You can override that in a CSS style sheet, but then you need to change the style sheets for each selected series so that the series color stays constant, which is a pain.
There may be a nicer way of handling this. I thought you might just be able to set the DEFAULT_COLOR_ looked up color via setStyle in code, but I couldn't get that to work, so I just went with the pretty ugly style sheet code.
I also didn't integrate the logarithmic axis, because (as far as I can tell) the axis type should really have no bearing on this.
Another trick to this was that animated must be turned off for it to work, otherwise, the observer for the animated data will be fired while resetting the series data. It will think there is duplicated data series being added (which there isn't really, so that is a strange implementation quirk of the chart animation logic).
Anyway, for what it's worth, I provided the code I came up with.
If no toggles are set, it shows all series. If a toggle is set, it only shows the series corresponding to that toggle.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.Random;
public class SeriesSelectionApp extends Application {
private static final int NUM_SERIES = 3;
private static final int NUM_DATA_PER_SERIES = 10;
private static final int DATA_MIN_VALUE = 5;
private static final int DATA_MAX_VALUE = 10;
private static final String[] seriesColors = new String[] {
"red", "green", "blue"
};
private static final Random random = new Random(42);
#Override
public void start(Stage stage) {
ObservableList<XYChart.Series<Number, Number>> data = generateData();
LineChart<Number, Number> lineChart = new LineChart<>(
new NumberAxis(0, NUM_DATA_PER_SERIES, 1),
new NumberAxis(0, DATA_MAX_VALUE, 1)
);
lineChart.getData().setAll(data);
lineChart.setAnimated(false);
setDefaultChartSeriesColors(lineChart);
HBox controls = new HBox(10);
ToggleGroup seriesSelectionToggleGroup = new ToggleGroup();
for (int i = 0; i < NUM_SERIES; i++) {
ToggleButton showSeriesToggleButton = new ToggleButton("Series " + (i+1));
showSeriesToggleButton.setToggleGroup(seriesSelectionToggleGroup);
showSeriesToggleButton.setUserData(i+1);
controls.getChildren().add(showSeriesToggleButton);
}
seriesSelectionToggleGroup.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> {
lineChart.getData().clear();
if (selectedToggle == null) {
lineChart.getData().addAll(data);
setDefaultChartSeriesColors(lineChart);
} else {
int selectedSeriesNum = (int) selectedToggle.getUserData();
lineChart.getData().add(data.get(selectedSeriesNum - 1));
setSpecificChartSeriesColor(lineChart, selectedSeriesNum);
}
});
VBox layout = new VBox(10, controls, lineChart);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private void setDefaultChartSeriesColors(LineChart<Number, Number> lineChart) {
lineChart.getStylesheets().setAll(
"""
data:text/css,
.default-color0.chart-line-symbol { -fx-background-color: red, white; }
.default-color1.chart-line-symbol { -fx-background-color: green, white; }
.default-color2.chart-line-symbol { -fx-background-color: blue, white; }
.default-color0.chart-series-line { -fx-stroke: red; }
.default-color1.chart-series-line { -fx-stroke: green; }
.default-color2.chart-series-line { -fx-stroke: blue; }
"""
);
}
private void setSpecificChartSeriesColor(LineChart<Number, Number> lineChart, int seriesNum) {
lineChart.getStylesheets().setAll(
"""
data:text/css,
.default-color0.chart-line-symbol { -fx-background-color: MY_COLOR, white; }
.default-color0.chart-series-line { -fx-stroke: MY_COLOR; }
""".replaceAll("MY_COLOR", getSeriesColor(seriesNum))
);
}
private String getSeriesColor(int seriesNum) {
return seriesColors[(seriesNum - 1) % seriesColors.length];
}
private ObservableList<XYChart.Series<Number, Number>> generateData() {
ObservableList<XYChart.Series<Number, Number>> allData = FXCollections.observableArrayList();
for (int seriesNum = 0; seriesNum < NUM_SERIES; seriesNum++) {
ObservableList<XYChart.Data<Number, Number>> seriesData = FXCollections.observableArrayList();
for (int x = 0; x < NUM_DATA_PER_SERIES; x++) {
int y = random.nextInt(DATA_MAX_VALUE - DATA_MIN_VALUE) + DATA_MIN_VALUE;
XYChart.Data<Number, Number> dataItem = new XYChart.Data<>(x, y);
seriesData.add(dataItem);
}
XYChart.Series<Number, Number> series = new XYChart.Series<>("Series " + (seriesNum + 1), seriesData);
allData.add(series);
}
return allData;
}
public static void main(String[] args) {
launch(args);
}
}

Related

Android barchart legends on top of the chart

So i have a barchart in MPandroid, and i need to put the names on the chart, but there is no space where i want them, the solution is to put them on top of the chart. Like this:
can someone help me achieve this ?
There isn't a built-in way of putting the text in a non-standard location, but you can do it by writing a custom renderer for your chart. If you extend the HorizontalBarChartRenderer class and override the drawValues method (making just a few modifications to the original version in the code linked) you can change where the label gets drawn.
Custom Renderer
Code copied from drawValues in HorizontalBarChartRenderer and simplified to only include required parts for this use-case (removed logic for stacked charts, icons, toggling values on and off, etc). The only change is to the x,y values passed to drawValue()
private static class MyRenderer extends HorizontalBarChartRenderer {
public MyRenderer(BarDataProvider chart, ChartAnimator animator,
ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
#Override
public void drawValues(Canvas c) {
List<IBarDataSet> dataSets = mChart.getBarData().getDataSets();
final float valueOffsetPlus = Utils.convertDpToPixel(5f);
for (int i = 0; i < mChart.getBarData().getDataSetCount(); i++) {
IBarDataSet dataSet = dataSets.get(i);
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
ValueFormatter formatter = dataSet.getValueFormatter();
// get the buffer
BarBuffer buffer = mBarBuffers[i];
for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 1]))
break;
if (!mViewPortHandler.isInBoundsX(buffer.buffer[j]))
continue;
if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1]))
continue;
BarEntry entry = dataSet.getEntryForIndex(j / 4);
String formattedValue = formatter.getBarLabel(entry);
// Modify the x, y position here to control where the
// text is. The "buffer" array gives the positions of
// the current bar (in pixels)
drawValue(c,
formattedValue,
buffer.buffer[j] + valueOffsetPlus,
buffer.buffer[j+1] - valueOffsetPlus,
dataSet.getValueTextColor(j / 2));
}
}
}
}
Example Use
To use the custom renderer, set it on the chart and modify the bar widths to leave room for the text.
HorizontalBarChart chart = findViewById(R.id.chart);
List<Float> values = Arrays.asList(3f, 2f, 2.2f);
List<Integer> colors = Arrays.asList(Color.CYAN, Color.RED, Color.GREEN);
List<String> labels = Arrays.asList("Pickled Horses", "Horses", "Pickles");
List<BarEntry> entries = new ArrayList<>();
for(int i = 0; i < values.size(); ++i) {
entries.add(new BarEntry(i+1, values.get(i), labels.get(i)));
}
BarDataSet ds = new BarDataSet(entries, "Values");
ds.setColors(colors);
ds.setValueTextSize(14f);
ds.setValueFormatter(new ValueFormatter() {
#Override
public String getBarLabel(BarEntry barEntry) {
return (String)barEntry.getData();
}
});
ds.setAxisDependency(YAxis.AxisDependency.RIGHT);
BarData data = new BarData(ds);
// Reduce bar width to leave room for text
data.setBarWidth(0.75f);
// Use the custom renderer
chart.setRenderer(new MyRenderer(chart, chart.getAnimator(), chart.getViewPortHandler()));
// Misc formatting & setup
chart.setData(data);
chart.getAxisLeft().setEnabled(false);
chart.getAxisRight().setAxisMinimum(0f);
chart.getAxisRight().setAxisMaximum(4f);
chart.getAxisRight().setGranularity(1f);
chart.getAxisRight().setTextSize(14f);
chart.getAxisRight().setDrawGridLines(false);
chart.getXAxis().setDrawLabels(false);
chart.getXAxis().setDrawGridLines(false);
chart.getXAxis().setAxisMaximum(3.6f);
chart.getDescription().setEnabled(false);
chart.getLegend().setEnabled(false);
chart.setBorderColor(Color.BLACK);
chart.setBorderWidth(1f);
chart.setDrawBorders(true);

Last value from dice JAVAFX

I normally don't go on sites like these but I got a serious nooby question and can't really find out how to fix this.
So I have a simple dice that rolls when you hit a button. It prints out all the dice numbers in the console. Now I want to save the last int so I can do calculations etc with it. Now the problem is I have serious no idea how to get the last int of the dice and print it out in the console. Does anyone have experience in this and can help me? Here is my code:
btn.setText("Roll Die");
btn.setOnAction((ActionEvent event) -> {
btn.setDisable(true);//Disable Button
Random random = new Random();
int gekozen = Integer.parseInt(tf3.getText());
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(.3), (actionEvent) -> {
int tempRandom = random.nextInt(6) + 1;
System.out.println(tempRandom);
die.setDieFace(tempRandom);
}));
timeline.setCycleCount(random.nextInt(20) + 1);
timeline.play();
timeline.setOnFinished(actionEvent -> {
btn.setDisable(false);//Enable Button
});
});
The best option is just to store the values somewhere. Your Die class (or whatever the class die is from) seems like a good candidate for that. In a well designed class, if you pass a value to a set method, say setXyz(x), then calling getXyz(), with no operations on the same object in the meantime, will return the same value x. So if you have designed your class following standard Java patterns, you can just do
timeline.setCycleCount(random.nextInt(20) + 1);
timeline.play();
timeline.setOnFinished(actionEvent -> {
btn.setDisable(false);//Enable Button
int dieValue = die.getDieFace();
System.out.println(dieValue);
});
Another option is to compute all the temporary die values ahead of time:
btn.setText("Roll Die");
btn.setOnAction((ActionEvent event) -> {
btn.setDisable(true);//Disable Button
Random random = new Random();
int gekozen = Integer.parseInt(tf3.getText());
int numTempValues = random.nextInt(20) + 1 ;
int[] tempValues = random.ints(1, 7).limit(numTempValues).toArray();
int finalDieValue = tempValues[numTempValues - 1];
Timeline timeline = new Timeline() ;
for (int i = 0 ; i < tempValues.length ; i++) {
KeyFrame keyFrame = new KeyFrame(Duration.seconds(0.3).multiply(i+1), actionEvent -> {
System.out.println(tempValues[i]);
die.setDieFace(tempValues[i]);
});
timeline.getKeyFrames().add(keyFrame);
}
timeline.setOnFinished(actionEvent -> {
btn.setDisable(false);//Enable Button
System.out.println("You rolled: "+finalDieValue);
});
timeline.play();
});
You can add an int variable to the Die class.
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
/**
*
* #author blj0011
*/
public class Die
{
ImageView dieFace;
Image[] images;
int dieValue;
public Die(Image[] images)
{
this.images = images;
dieFace = new ImageView(this.images[0]);//set default to image 0
dieValue = 1;
}
public Die(Image[] images, int dieFaceValue)
{
//Need to catch for values less than 1 and greater than 6!
this.images = images;
dieFace = new ImageView(this.images[dieFaceValue - 1]);
dieValue = dieFaceValue;
}
public ImageView getdieFace()
{
return dieFace;
}
public int getDieValue()
{
return dieValue;
}
public void setDieFace(int dieFaceValue)
{
//Need to catch for values less than 1 and greater than 6!
dieFace.setImage(this.images[dieFaceValue - 1]);
dieValue = dieFaceValue;
}
}

How to use complex coefficient in apache FFT

I'm working with a basic example of FFT with Apache Commons library. I have two classes:
public class Fourier {
private static XYSeries data;
private static XYSeriesCollection collection;
Fourier(){
collection =new XYSeriesCollection();
createSquare();
createFourier();
showGraph();
}
private static void createSquare(){
data=new XYSeries("Dati");
for(double i=-8;i<8;i+=1d/128){
data.add(i,((i<-4||(i<4&&i>0)?1:0)));
//data.add(i,(i<0?i+1:-i+1));
}
collection.addSeries(data);
}
private static void createFourier(){
double[] arrayFourier= new double[data.getItemCount()];
for(int i=0;i<data.getItemCount();i++){
arrayFourier[i]=data.getDataItem(i).getYValue();
}
FastFourierTransformer transformer=new FastFourierTransformer(DftNormalization.STANDARD);
Complex[] coeff=transformer.transform(arrayFourier, TransformType.INVERSE);
double norm = 0;
for(Complex Z: coeff){
System.out.println(Z.abs()+"\t"+Z.toString());
norm+=(Z.abs())*(Z.abs());
}
System.out.println(norm);
XYSeries fourier=new XYSeries("Fourier");
FourierSeries series=new FourierSeries(coeff,8);
for(double i=data.getMinX();i<data.getMaxX();i+=0.05){
fourier.add(i,series.getSeries(i));
}
collection.addSeries(fourier);
}
private static void showGraph(){
JFreeChart chart = ChartFactory.createXYLineChart("Fourier", "x", "f(x)", collection, PlotOrientation.VERTICAL, true, false, false);
ChartFrame window=new ChartFrame("Fourier", chart, false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
}
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
#Override
public void run() {
new Fourier();
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.exit(0);
}
});
t.start();
}
}
and another one
public class FourierSeries {
Complex[] coeff;
double T;
public FourierSeries(Complex[] coeff, double T) {
this.coeff=coeff;
this.T=T;
}
public double getSeries(double x){
double k=Math.PI/T;
double value=0; //primo coefficiente
int i=0;
/*for(Complex iter:coeff){
if (i!=0&&i<coeff.length/2.) {
if (i % 2 == 0) {
value += iter.abs() * Math.cos(k * i * x);
System.out.println(iter.abs()+"cos"+i);
} else {
value += iter.abs() * Math.sin(k * i * x); //npari (i dispari) modulo*cos()
System.out.println(iter.abs()+"sin"+i);
}
}
i++;
}*/
for (Complex iter : coeff) {
if(i<coeff.length/2.)
value+=iter.getReal()*Math.cos(x*k*i)+iter.getImaginary()*Math.sin(x*k*i);
i++;
}
return value;
}
}
I introduce in my createSquare() method a function (square wave).
In order I do the following actions:
I perform the FFT.
Can you explain me how the array returned by transform.transform(array, TransformType) is composed?
Why the Fourier series is not correctly drawn?
The inverse series is correctly a square wave with same frequency as initial wave, but not correctly scaled on y-axsis. What is wrong? This is the output:
In transforming the complex form of the Fourier series to the real form, you have to treat the constant term differently. Or in the case of the code in question, you have to correct the non-constant coefficients by a factor of 2. So that you get 0.5 +- 0.5 as result, not 0.5 +- 0.25 as is currently the case.
You are using the inverse transform, where the resulting coefficients can then be interpreted as
f(x) approx sum(m = -N/2+1, N/2-1) c[k]*exp(-i*m*k*x)
For index m>=1 you combine two complex conjugate pairs of terms, the resulting real terms are, using c[k]=a+ib,
(a+i*b)*exp(-i*m*k*x) + (a-i*b)*exp(+i*m*k*x)
= 2*a*cos(m*k*x) + 2*b*sin(m*k*x)
Your Fourier series computation should thus look like
value = coeff[0].getReal();
for (int m=1; m<coeff.length/2; m++) {
value += 2*coeff[m].getReal()*Math.cos(x*k*m)
+ 2*coeff[m].getImaginary()*Math.sin(x*k*m);
}

Eclipse Error: Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException

I am working on a Java program called Namesurfer for a home assignment. The program consists of five classes: 'NameSurfer, NameSurferConstants, NameSurferDataBase, NameSurferEntry, and NameSurferGraph.
The code is (I thought) complete, but when I put the name on NameSurfer console and press enter, I get the following error. And when I click Graph, it doesn't do anything.
My suspicion is it has something to do with NameSurferEntry class, but I've been looking for hours without success. I'm really new at Java, and any help will be appreciated.
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at NameSurfer.actionPerformed(NameSurfer.java:58)
at javax.swing.JTextField.fireActionPerformed(JTextField.java:492)
at javax.swing.JTextField.postActionEvent(JTextField.java:705)
at javax.swing.JTextField$NotifyAction.actionPerformed(JTextField.java:820)
at javax.swing.SwingUtilities.notifyAction(SwingUtilities.java:1645)
at javax.swing.JComponent.processKeyBinding(JComponent.java:2859)
at javax.swing.JComponent.processKeyBindings(JComponent.java:2894)
at javax.swing.JComponent.processKeyEvent(JComponent.java:2822)
at java.awt.Component.processEvent(Component.java:6159)
at java.awt.Container.processEvent(Container.java:2083)
at java.awt.Component.dispatchEventImpl(Component.java:4744)
at java.awt.Container.dispatchEventImpl(Container.java:2141)
at java.awt.Component.dispatchEvent(Component.java:4572)
at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1856)
at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:722)
at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:1000)
at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:865)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:686)
at java.awt.Component.dispatchEventImpl(Component.java:4616)
at java.awt.Container.dispatchEventImpl(Container.java:2141)
at java.awt.Window.dispatchEventImpl(Window.java:2489)
at java.awt.Component.dispatchEvent(Component.java:4572)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:710)
at java.awt.EventQueue.access$400(EventQueue.java:82)
at java.awt.EventQueue$2.run(EventQueue.java:669)
at java.awt.EventQueue$2.run(EventQueue.java:667)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:98)
at java.awt.EventQueue$3.run(EventQueue.java:683)
at java.awt.EventQueue$3.run(EventQueue.java:681)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:680)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:296)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:211)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:196)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:188)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Here's the code for NameSurfer.
/*
* File: NameSurfer.java
* ---------------------
* When it is finished, this program will implements the viewer for
* the baby-name database described in the assignment handout.
*/
import acm.program.*;
import java.awt.event.*;
import javax.swing.*;
public class NameSurfer extends Program implements NameSurferConstants {
/* private instance variables*/
private JButton graphButton;
private JButton clearButton;
private JLabel nameLabel;
private JTextField name;
private NameSurferGraph graph;
private NameSurferDataBase dataBase;
/**
* This method has the responsibility for reading in the data base
* and initializing the interactors at the top of the window.
*/
public void init() {
addActionListeners();
graph = new NameSurferGraph();
add(graph);
/* adds the control bar*/
nameLabel = new JLabel ("Name");
add(nameLabel, NORTH);
name = new JTextField(MAX_FONT_NAME);
name.addActionListener(this);
add(name, NORTH);
graphButton = new JButton ("Graph");
add(graphButton, NORTH);
clearButton = new JButton ("Clear");
add(clearButton, NORTH);
}
/**
* This class is responsible for detecting when the buttons are
* clicked, so you will have to define a method to respond to
* button actions.
*/
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals ("Clear")) {
graph.clear();
graph.update();
} else {
String inputName = name.getText();
NameSurferEntry entry = dataBase.findEntry(inputName);
if (entry != null) {
graph.addEntry(entry);
graph.update();
}
}
}
}
And here's the code for NameSurferEntry.
/*
* File: NameSurferEntry.java
* --------------------------
* This class represents a single entry in the database. Each
* NameSurferEntry contains a name and a list giving the popularity
* of that name for each decade stretching back to 1900.
*/
import acm.util.*;
import java.util.*;
import java.util.StringTokenizer;
public class NameSurferEntry implements NameSurferConstants {
/* private instance variables*/
private String name;
private int[] ranks = new int [NDECADES];
/**
* Creates a new NameSurferEntry from a data line as it appears
* in the data file. Each line begins with the name, which is
* followed by integers giving the rank of that name for each
* decade.
*/
public NameSurferEntry(String line) {
//gets the name
int nameEnd = line.indexOf(" ");
name = line.substring(0, nameEnd);
//gets the ranking and forms it into an array using StringTokenizer class
String rankingStart = line.substring(nameEnd + 1);
StringTokenizer tokenizer = new StringTokenizer(rankingStart);
for (int i = 0; tokenizer.hasMoreTokens(); i++) {
int yearRank = Integer.parseInt(tokenizer.nextToken());
ranks[i] = yearRank;
}
}
/* Method: getName() */
/**
* Returns the name associated with this entry.
*/
public String getName() {
return name;
}
/* Method: getRank(decade) */
/**
* Returns the rank associated with an entry for a particular
* decade. The decade value is an integer indicating how many
* decades have passed since the first year in the database,
* which is given by the constant START_DECADE. If a name does
* not appear in a decade, the rank value is 0.
*/
public int getRank(int decade) {
if (decade <NDECADES) {
return ranks[decade];
}
return 0;
}
/* Method: toString() */
/**
* Returns a string that makes it easy to see the value of a
* NameSurferEntry.
*/
public String toString() {
String result = "";
for (int i = 0; i < ranks.length; i++) {
result += getRank(i);
}
return ("\"" + name + "[" + result + "]\"");
}
}
And here's the code for NameSurferGraph.
import acm.graphics.*;
import java.awt.event.*;
import java.util.*;
import java.awt.*;
public class NameSurferGraph extends GCanvas implements NameSurferConstants, ComponentListener {
/*Private instance variables*/
private ArrayList <NameSurferEntry> dataDisplay;
/**
* Creates a new NameSurferGraph object that displays the data.
*/
public NameSurferGraph() {
addComponentListener(this);
dataDisplay = new ArrayList<NameSurferEntry>();
}
/**
* Clears the list of name surfer entries stored inside this class.
*/
public void clear() {
dataDisplay.clear();
update();
}
/**
* Adds a new NameSurferEntry to the list of entries on the display.
* Note that this method does not actually draw the graph, but
* simply stores the entry; the graph is drawn by calling update.
*/
public void addEntry(NameSurferEntry entry) {
dataDisplay.add(entry);
}
/**
* Updates the display image by deleting all the graphical objects
* from the canvas and then reassembling the display according to
* the list of entries. Your application must call update after
* calling either clear or addEntry; update is also called whenever
* the size of the canvas changes.
*/
public void update() {
removeAll();
drawGraph();
if (dataDisplay.size() >= 0) {
for (int i = 0; i < dataDisplay.size(); i++) {
NameSurferEntry entry = dataDisplay.get(i);
drawRankingGraph (entry, i);
}
}
}
/*draws the background grids and displays the years*/
private void drawGraph() {
drawMargins();
drawVerticalLines();
displayYears();
}
/*Draws the horizontal lines at the top and the bottom of the window*/
private void drawMargins() {
double x1 = 0;
double x2 = getWidth();
double y1 = GRAPH_MARGIN_SIZE;
double y2 = getHeight() - GRAPH_MARGIN_SIZE;
GLine topLine = new GLine (x1, y1, x2, y1);
GLine bottomLine = new GLine (x1, y2, x2, y2);
add(topLine);
add(bottomLine);
}
/*Draws the vertical lines*/
private void drawVerticalLines() {
double x = 0;
for (int i = 0; i < NDECADES; i++) {
GLine verticalLine = new GLine (x, 0, x, getHeight());
x += getWidth() / NDECADES;
add(verticalLine);
}
}
/*Displays the years*/
private void displayYears() {
int decade = START_DECADE;
double x = 0;
for (int i = 0; i < NDECADES; i++) {
GLabel label = new GLabel ("" + decade);
add(label, x, getHeight() - GRAPH_MARGIN_SIZE/2 + (label.getAscent() / 2));
decade += NUMBER_OF_YEARS;
x += getWidth() / NDECADES;
}
}
/*Draws the ranking graph and the input name label*/
private void drawRankingGraph(NameSurferEntry entry, int n) {
int inputOrder = n;
for (int i = 0; i < NDECADES - 1; i++) {
int r1 = entry.getRank(i);
int r2 = entry.getRank(i + 1);
double x1 = i * (getWidth()/NDECADES);
double x2 = (i+1) * (getWidth()/NDECADES);
double y1 = 0;
double y2 = 0;
if (r1 == 0) {
y1 = getHeight() - GRAPH_MARGIN_SIZE;
} else {
y1 = GRAPH_MARGIN_SIZE + (getHeight() - GRAPH_MARGIN_SIZE*2) * r1 / MAX_RANK;
}
if (r2 == 0) {
y2 = getHeight() - GRAPH_MARGIN_SIZE;
} else {
y2 = GRAPH_MARGIN_SIZE + (getHeight() - GRAPH_MARGIN_SIZE*2) * r2 / MAX_RANK;
}
/*Sets the graph and the label on the window*/
GLine rankingGraph = new GLine (x1, y1, x2, y2);
GLabel inputName = new GLabel(entry.getName() + " " + (entry.getRank(i) == 0 ? "*" : entry.getRank(i)));
/*Sets the color*/
Color color = getColor(inputOrder%4);
rankingGraph.setColor(color);
inputName.setColor(color);
/*Displays the graph and the label*/
add(rankingGraph);
add(inputName, x1, y2);
}
}
/*Gets the color of the rankingGraph and the inputName label*/
private Color getColor(int i) {
switch (i) {
case 0: return Color.black;
case 1: return Color.red;
case 2: return Color.blue;
}
return Color.magenta;
}
/* Implementation of the ComponentListener interface */
public void componentHidden(ComponentEvent e) { }
public void componentMoved(ComponentEvent e) { }
public void componentResized(ComponentEvent e) { update(); }
public void componentShown(ComponentEvent e) { }
}
Your "database" seems to be null.
Note that you either provided incomplete NameSurfer source or you need to recompile your application - line numbers are off, line 58 only has a closing brace.
Check the usage of "actionPerformed" method. You're probably trying to use it on something which is not set and therefore "null".
You're calling a method on an object that's null at NameSurfer.java:58 . Find out what can be null on that line and figure out why it's null even though you expect it not to be.
Unrelated to that, read up on the Java Naming Conventions. Only your type names should start with an uppercase.

Having trouble getting labels to appear next to pie chart legend in a Java Applet

I'm fairly new to Java, and using NetBeans IDE 7.0.1.
Problem:
I'm trying to finish up a Java applet I've been working on that requires a pie chart. I've implemented the pie chart, but I've not been able to get the text labels to appear next to the data in the legend. Does anyone have any pointers?
package piechartapplet;
import javax.swing.*;
import java.awt.*;
public class PieChartApplet extends JApplet {
int TotalPieChartSlices = 7;
SliceValues[] pieSlice = new SliceValues[TotalPieChartSlices];
private int pieChartValueY;
public PieChartApplet()
{
//Source for input statisctics:
//Global Issues. (2012). World Military Spending. Retrieved from http://www.globalissues.org/article/75/world-military-spending
// Link: http://www.globalissues.org/article/75/world-military-spending
pieSlice[0] = new SliceValues(41.0, Color.RED,"United States");
pieSlice[1] = new SliceValues(8.2, Color.CYAN,"China");
pieSlice[2] = new SliceValues(4.1, Color.GREEN,"Russia");
pieSlice[3] = new SliceValues(3.6, Color.BLUE,"UK");
pieSlice[4] = new SliceValues(3.6, Color.PINK,"France");
pieSlice[5] = new SliceValues(21.3, Color.ORANGE,"Next 10 Countries Combined");
pieSlice[6] = new SliceValues(18.2, Color.LIGHT_GRAY,"Rest of the World");
}
// drawing the pir chart using the values in the array
public int drawPieChartValues(Graphics2D graphics, Rectangle pieChartArea, SliceValues[] pieSlice)
{
// setting font size/style
Font font = new Font("Arial", Font.BOLD, 24);
graphics.setFont(font);
// Title of Pie Chart
graphics.drawString("World Military Spending (% by Country)", 20, 20);
graphics.setFont(font);
// establishing inital area positioning
pieChartArea.x=10;
pieChartArea.y = 30;
// using the array values, rectangles, and color to draw the slices
for(int i=0; i<pieSlice.length;i++)
{
graphics. setColor(pieSlice[i].getSliceColor());
graphics.fillRect(pieChartArea.x, pieChartArea.y, 15, 10);
graphics.setColor(Color.BLACK);
pieChartArea.y+=20;
graphics.drawString(""+pieSlice[i].getSliceValue(), pieChartArea.x+25, pieChartArea.y-10);
}
return pieChartArea.y+=10;
}
//The code below was adapted from an example I found that enables me to pull from
// the array and use the values as the slice sizes, putting them into a 360* pie
// Walker, K. (2012). How to Draw a Pie Chart in Java. Retrieved from http://www.ehow.com/how_6647263_draw-pie-chart-java.html
public void drawPieChart(Graphics2D graphics, Rectangle pieChartArea, SliceValues[] pieSlice) {
// pulling array data for the individual slices
double total = 0.0;
for (int i=0; i<pieSlice.length; i++)
{
total += pieSlice[i].getSliceValue(); //pulling value
}
// drawing the slice and positioning it accordingly
double slice = 0.0D;
int StartAngle = 0;
pieChartArea.x = 20;
for (int i=0; i<pieSlice.length; i++) {
// finding initial and final angels
StartAngle = (int)(slice * 360 / total);
int finalAngle = (int)(pieSlice[i].getSliceValue() * 360 / total);
//loop for last slice
if (i == pieSlice.length-1)
{
finalAngle = 360 - StartAngle;
}
// Pulling color from array and setting accordingly
graphics.setColor(pieSlice[i].getSliceColor()); //pulling color
// drawing pie piece
graphics.fillArc(pieChartArea.x, pieChartValueY, pieChartArea.width/2, pieChartArea.height/2, StartAngle, finalAngle);
slice += pieSlice[i].getSliceValue();
}
}
public void paint(Graphics g)
{
super.paint(g);
pieChartValueY = drawPieChartValues((Graphics2D)g, getBounds(), pieSlice);
drawPieChart((Graphics2D)g, getBounds(), pieSlice);
}
public void init() {
// Sizing my applet
setSize(600,600);
// adding applet to pane
getContentPane().add(new PieChartApplet());
}
}
Here is the 'values' code
package piechartapplet;
import javax.swing.*;
import java.awt.*;
public class SliceValues
{
// Establishing values for the pir chart
private double Slicevalue;
private Color Slicecolor;
private String Slicestring;
// Construction begins...
public SliceValues(double value, Color color, String string) {
this.Slicevalue = value; //values from array
this.Slicecolor = color; //color from array
this.Slicestring = string; //string values
}
// calling slice values, colors, strings, and setting values, colors, strings for each slice
public double getSliceValue() {
return Slicevalue;
}
public void setSliceValue(double value) {
this.Slicevalue = value;
}
public Color getSliceColor() {
return Slicecolor;
}
public void setSliceColor(Color color) {
this.Slicecolor = color;
}
public String getSliceString() {
return Slicestring;
}
public void setSliceString(String string) {
this.Slicestring = string;
}
}
The source code for PieChartDemo1, illustrated here with labels, is included in the distribution.

Categories