0

I'm trying to add a horizontal line to a linechart in JavaFX. Found this post in SO and now trying to modify it to work in my case, however I'm getting an error, and hopefully someone can see what is the problem here.

Here is the controller code:

package gems.view;

public class GUImainController implements Initializable, Observer
{
     final CategoryAxis xAxis = new CategoryAxis();
     final NumberAxis yAxis = new NumberAxis();

     private ArrayList<Float> tempArr = new ArrayList<>();
     private ArrayList<String> tempTimeArray = new ArrayList<>();

     private Series<String, Number> tempChart = new Series<>();

     @FXML
     private LineChartWithMarker<String, Number> lcTemperature = new LineChartWithMarker<>(xAxis,yAxis);

     @Override
     public void initialize(URL arg0, ResourceBundle arg1) 
     {      
          yAxis.setPrefWidth(35);   

          xAxis.setLabel("Temperature \\u00b0");
          yAxis.setLabel("Time");

          tempChart = new XYChart.Series<String, Number>();

          lcTemperature.getData().add(tempChart);


tempArr.add(observable.getLatestMeasurementsFromServer().getTemperature());
    tempTimeArray.add(observable.getLatestMeasurementsFromServer().getDateTime().getTime());

          for (int i = 0; i < tempArr.size(); i++)
          {
                tempChart.getData().add(new XYChart.Data<>(tempTimeArray.get(i), tempArr.get(i)));
          }         

                tempChart.getNode().lookup(".chart-series-line").setStyle("-fx-stroke: orange;");

    String s = "test";

    Data<String, Number> verticalMarker = new Data<>(s, 20);
    lcTemperature.addHorizontalValueMarker(verticalMarker);

 }

Here is the LineChartWithMarker class:

package gems.view;

import java.util.Objects;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.shape.Line;

public class LineChartWithMarker<X,Y> extends LineChart
{
private ObservableList<Data<X, Y>> horizontalMarkers;   

public LineChartWithMarker(CategoryAxis xAxis, NumberAxis yAxis) 
{
    super(xAxis, yAxis);

    horizontalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()});
    horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}

public void addHorizontalValueMarker(Data<X, Y> marker) 
{
    Objects.requireNonNull(marker, "the marker must not be null");
    if (horizontalMarkers.contains(marker)) return;
    Line line = new Line();
    marker.setNode(line );
    getPlotChildren().add(line);
    horizontalMarkers.add(marker);
}

public void removeHorizontalValueMarker(Data<X, Y> marker) {
    Objects.requireNonNull(marker, "the marker must not be null");
    if (marker.getNode() != null) {
        getPlotChildren().remove(marker.getNode());
        marker.setNode(null);
    }
    horizontalMarkers.remove(marker);
}

@Override
protected void layoutPlotChildren() {
    super.layoutPlotChildren();
    for (Data<X, Y> horizontalMarker : horizontalMarkers) {
        Line line = (Line) horizontalMarker.getNode();
        line.setStartX(0);
        line.setEndX(getBoundsInLocal().getWidth());
        line.setStartY(getYAxis().getDisplayPosition(horizontalMarker.getYValue()) + 0.5); // 0.5 for crispness
        line.setEndY(line.getStartY());
        line.toFront();
    }   
}   
}

And here is the FXML (just the part for line chart):

                          <LineChart fx:id="lcTemperature" minWidth="100.0" prefHeight="155.0" prefWidth="708.0" BorderPane.alignment="CENTER">
                            <xAxis>
                              <CategoryAxis side="BOTTOM" />
                            </xAxis>
                            <yAxis>
                              <NumberAxis autoRanging="false" lowerBound="20.0" minorTickCount="10" minorTickLength="10.0" side="LEFT" tickLabelGap="10.0" tickLength="20.0" tickUnit="1.0" upperBound="30.0" />
                            </yAxis>
                          </LineChart>

Herew is the error stack:

Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$154(LauncherImpl.java:182)
at java.lang.Thread.run(Unknown Source)
Caused by: javafx.fxml.LoadException: 
/C:/My%20Projects/GEMS_SEP2/GEMS_SEP2/bin/FXML/GUIgems.fxml:42

at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
at gems.view.GUIuserClient.start(GUIuserClient.java:33)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
... 1 more
Caused by: java.lang.IllegalArgumentException: Can not set gems.view.LineChartWithMarker field gems.view.GUImainController.lcTemperature to javafx.scene.chart.LineChart
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
at sun.reflect.UnsafeObjectFieldAccessorImpl.set(Unknown Source)
at java.lang.reflect.Field.set(Unknown Source)
at javafx.fxml.FXMLLoader.injectFields(FXMLLoader.java:1163)
at javafx.fxml.FXMLLoader.access$1600(FXMLLoader.java:103)
at javafx.fxml.FXMLLoader$ValueElement.processValue(FXMLLoader.java:857)
at javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:765)
at javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2823)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2532)
... 12 more
Exception running application gems.view.GUIuserClient

By the way, GUIgems.fxml:42 is the last line of the FXML code I have posted above.

Also, its maybe worth mentioning that the line chart works fine if I am using a simple LineChart, like this:

@FXML
private LineChart<String, Number> lcTemperature;

It does not work when I write like this:

@FXML
private LineChartWithMarker<String, Number> lcTemperature = new LineChartWithMarker<>(xAxis,yAxis);
RollerMobster
  • 864
  • 1
  • 10
  • 28
  • 1
    Focus on this stacktrace line : `Caused by: java.lang.IllegalArgumentException: Can not set gems.view.LineChartWithMarker field gems.view.GUImainController.lcTemperature to javafx.scene.chart.LineChart`. – Pagbo May 31 '18 at 09:21
  • @Pagbo Yup, I noticed this one. At the moment I am trying this: In the FXML, change LineChart to LineChartWithMarker and then import the custom class, but its giving me an error that class not found, and I am really not sure if this is the right thing to do.. any ideas? – RollerMobster May 31 '18 at 09:24

1 Answers1

1

Alright so I managed to find a solution, if anyone will find themselves in the same situation.

Constructor of LineChartWithMarker needs to look like this:

public LineChartWithMarker(@NamedArg("xAxis") Axis<X> xAxis, 
        @NamedArg("yAxis") Axis<Y> yAxis) 
{
    super(xAxis, yAxis);

    horizontalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()});
    horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}

And the FXML needs to look like this:

<?import gems.view.LineChartWithMarker?>
<LineChartWithMarker fx:id="lcTemperature" minWidth="100.0" prefHeight="155.0" prefWidth="708.0" BorderPane.alignment="CENTER">
                            <xAxis>
                              <CategoryAxis side="BOTTOM" />
                            </xAxis>
                            <yAxis>
                              <NumberAxis autoRanging="false" lowerBound="20.0" minorTickCount="10" minorTickLength="10.0" side="LEFT" tickLabelGap="10.0" tickLength="20.0" tickUnit="1.0" upperBound="30.0" />
                            </yAxis>
                          </LineChartWithMarker>
RollerMobster
  • 864
  • 1
  • 10
  • 28
  • 1
    I hope you have also changed this line `@FXML private LineChartWithMarker lcTemperature = new LineChartWithMarker<>(xAxis,yAxis);` by this one `@FXML private LineChartWithMarker lcTemperature;` – Pagbo May 31 '18 at 09:54
  • @Pagbo Yes I did. By the way, I know its a different question, but maybe you know how to change the color of the marker itself? – RollerMobster May 31 '18 at 09:58
  • Try with `line.setStroke(COLOR_YOU_WANT);` – Pagbo May 31 '18 at 10:09