Consider the following application:

I expect, by each time clicking on BUTTON, the first data of the LineChart be removed. The output after the clicks must be as follows:
After 1st click

After 2nd click

After 3rd click

However, after the first click, I get the following ClassCastException:
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')
at javafx.controls@18/javafx.scene.chart.LineChart.createDataRemoveTimeline(LineChart.java:503)
at javafx.controls@18/javafx.scene.chart.LineChart.dataItemRemoved(LineChart.java:359)
at javafx.controls@18/javafx.scene.chart.XYChart.dataItemsChanged(XYChart.java:505)
at javafx.controls@18/javafx.scene.chart.XYChart$Series$1.onChanged(XYChart.java:1474)
at javafx.base@18/com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at javafx.base@18/com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.base@18/javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:239)
at javafx.base@18/javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.base@18/javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.base@18/javafx.collections.ObservableListBase.endChange(ObservableListBase.java:211)
at javafx.base@18/javafx.collections.ModifiableObservableListBase.remove(ModifiableObservableListBase.java:190)
at com.example.demo/com.example.demo.MyApplication.lambda$start$0(MyApplication.java:21)
at javafx.base@18/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at javafx.base@18/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
at javafx.base@18/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at javafx.base@18/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at javafx.base@18/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at javafx.base@18/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@18/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base@18/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@18/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base@18/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@18/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at javafx.base@18/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.base@18/javafx.event.Event.fireEvent(Event.java:198)
at javafx.graphics@18/javafx.scene.Node.fireEvent(Node.java:8797)
at javafx.controls@18/javafx.scene.control.Button.fire(Button.java:203)
at javafx.controls@18/com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:208)
at javafx.controls@18/com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
at javafx.base@18/com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:247)
at javafx.base@18/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at javafx.base@18/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
at javafx.base@18/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at javafx.base@18/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at javafx.base@18/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at javafx.base@18/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@18/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base@18/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@18/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base@18/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@18/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at javafx.base@18/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.base@18/javafx.event.Event.fireEvent(Event.java:198)
at javafx.graphics@18/javafx.scene.Scene$MouseHandler.process(Scene.java:3881)
at javafx.graphics@18/javafx.scene.Scene.processMouseEvent(Scene.java:1874)
at javafx.graphics@18/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2607)
at javafx.graphics@18/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:411)
at javafx.graphics@18/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:301)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics@18/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:450)
at javafx.graphics@18/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
at javafx.graphics@18/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:449)
at javafx.graphics@18/com.sun.glass.ui.View.handleMouseEvent(View.java:551)
at javafx.graphics@18/com.sun.glass.ui.View.notifyMouse(View.java:937)
at javafx.graphics@18/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics@18/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
at java.base/java.lang.Thread.run(Thread.java:833)
The code is as follows:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class MyApplication extends Application {
@Override
public void start(Stage stage) {
LineChart<String,String> lineChart=createLineChart();
Button button = new Button("BUTTON");
button.setOnAction(event -> lineChart.getData().get(0).getData().remove(0));
VBox vBox=new VBox(button,lineChart);
Scene scene = new Scene(vBox);
stage.setScene(scene);
stage.show();
}
LineChart<String,String> createLineChart(){
LineChart<String, String> lineChart = new LineChart<>(new CategoryAxis(), new CategoryAxis());
lineChart.getData().add(new XYChart.Series<>());
for (int i = 0; i < 3; i++) {
lineChart.getData().get(0).getData().add(new XYChart.Data<>("X string %d".formatted(i+1), "Y string %d".formatted(i+1)));
}
lineChart.getXAxis().setTickLabelFont(new Font(20));
lineChart.getYAxis().setTickLabelFont(new Font(20));
return lineChart;
}
public static void main(String[] args) {
launch();
}
}
Question
Why does ClassCastException raise in conversion of String to Number when I have no Number type data?
This is a bug in the implementation of LineChart. I cannot find an active bug report for it at https://bugs.openjdk.org/secure/Dashboard.jspa.
The issue arises in the call to the method createDataRemoveTimeline(...) (found here in the current implementation) which is implemented as:
private Timeline createDataRemoveTimeline(final Data<X,Y> item, final Node symbol, final Series<X,Y> series) {
Timeline t = new Timeline();
// save data values in case the same data item gets added immediately.
XYValueMap.put(item, ((Number)item.getYValue()).doubleValue());
t.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, new KeyValue(item.currentYProperty(),
item.getCurrentY()), new KeyValue(item.currentXProperty(),
item.getCurrentX())),
new KeyFrame(Duration.millis(500), actionEvent -> {
if (symbol != null) getPlotChildren().remove(symbol);
removeDataItemFromDisplay(series, item);
XYValueMap.clear();
},
new KeyValue(item.currentYProperty(),
item.getYValue(), Interpolator.EASE_BOTH),
new KeyValue(item.currentXProperty(),
item.getXValue(), Interpolator.EASE_BOTH))
);
return t;
}
The implementation caches values that are removed (as I understand it this is needed in case the same values are added back while the animation for their removal is progressing) in a Map<Data<X,Y>, Double>. This assumes the y-value is a Number (note the cast in the second line of the method) which of course is not necessarily true. I think this should be implemented with a Map<Data<X,Y>, Y>, though I have not fully analyzed the code.
The createDataRemoveTimeline(...) method is called from the dataItemRemoved(...) method (invoked when an item is removed from one of the data series) but only if animated is set to true. Hence one work around is to turn off animation:
lineChart.setAnimated(false);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With