7

I have problems with adding a line at a defined position in JavaFX. The line has to be line a constant line as shown here: How to add a value marker to JavaFX chart?

My problem is, that my layout definition is a bit more complicated. Have a look:

chart

The important part is the one on the top. I want to have the line on the y=60 line. The left part with the RadioBoxes is a VBox. The part with the (Scatter-)Chart is a StackPane (because I want it to fill the rest of the width). Inside this StackPane is the chart and a Group. The only child of the Group is the line.

I think the problem is, that the StackPane centers the Group with the line above the chart. But I can't get the combination of layouts which 1. stretches the chart 2. sets the Line above the chart 3. doesn't center the line

I tried lots of combinations but I just can't get it the way I want. Does anybody have an idea?!

Community
  • 1
  • 1
JoeHut
  • 93
  • 1
  • 6
  • Hm the way I did it to 'add' a line to a scatter chart is to actually add a line chart and scatter chart to the stackedpane. Probably not the best method thou... – Perneel Jun 16 '14 at 12:20
  • I tried it this way, too. But I couldn't get it done because of the title, legend, the title of the axes etc... – JoeHut Jun 16 '14 at 12:30

2 Answers2

9

Unfortunately, ValueMarkers are not supported in XYCharts (that's probably the place in the hierarchy were it should be done) (Mis-)using data with constant value is a hack which might (or not) be acceptable/possible in some contexts.

A cleaner way out is a custom Chart that supports such markers. F.i. a custom ScatterChart like:

public class ScatterXChart<X, Y> extends ScatterChart<X, Y> {

    // data defining horizontal markers, xValues are ignored
    private ObservableList<Data<X, Y>> horizontalMarkers;

    public ScatterXChart(Axis<X> xAxis, Axis<Y> yAxis) {
        super(xAxis, yAxis);
        // a list that notifies on change of the yValue property
        horizontalMarkers = FXCollections.observableArrayList(d -> new Observable[] {d.YValueProperty()});
        // listen to list changes and re-plot
        horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
    }

    /**
     * Add horizontal value marker. The marker's Y value is used to plot a
     * horizontal line across the plot area, its X value is ignored.
     * 
     * @param marker must not be null.
     */
    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);
    }

    /**
     * Remove horizontal value marker.
     * 
     * @param horizontalMarker must not be null
     */
    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);
    }

    /**
     * Overridden to layout the value markers.
     */
    @Override
    protected void layoutPlotChildren() {
        super.layoutPlotChildren();
        for (Data<X, Y> horizontalMarker : horizontalMarkers) {
            double lower = ((ValueAxis) getXAxis()).getLowerBound();
            X lowerX = getXAxis().toRealValue(lower);
            double upper = ((ValueAxis) getXAxis()).getUpperBound();
            X upperX = getXAxis().toRealValue(upper);
            Line line = (Line) horizontalMarker.getNode();
            line.setStartX(getXAxis().getDisplayPosition(lowerX));
            line.setEndX(getXAxis().getDisplayPosition(upperX));
            line.setStartY(getYAxis().getDisplayPosition(horizontalMarker.getYValue()));
            line.setEndY(line.getStartY());

        }
    }
}

A snippet testing the chart (f.i. insert into the online example in oracle's tutorial):

// instantiate chart
NumberAxis xAxis = new NumberAxis(0, 10, 1);
NumberAxis yAxis = new NumberAxis(-100, 500, 100);        
ScatterXChart<Number,Number> sc = new ScatterXChart<>(xAxis,yAxis);
// .. fill with some data
...
// ui to add/change/remove a value marker
Data<Number, Number> horizontalMarker = new Data<>(0, 110);
Button add = new Button("Add Marker");  
add.setOnAction(e -> sc.addHorizontalValueMarker(horizontalMarker));
Slider move = new Slider(yAxis.getLowerBound(), yAxis.getUpperBound(), 0);
move.setShowTickLabels(true);
move.valueProperty().bindBidirectional(horizontalMarker.YValueProperty());
Button remove = new Button("Remove Marker");
remove.setOnAction(e -> sc.removeHorizontalValueMarker(horizontalMarker));

Addendum:

While I wouldn't recommend the approach in the related question (adding the marker line to the chart's parent and managing its position/length externally) it is possible to use it in resizable containers. The crucial thingies to make it work:

  • listen to chart's size/location changes and update the line appropriately
  • set the marker's managed property to false

in code (updateShift is the part in the original that calculates the yShift/lineX):

Pane pane = new StackPane(chart);
chart.widthProperty().addListener(o -> updateShift(chart));
chart.heightProperty().addListener(o -> updateShift(chart));
valueMarker.setManaged(false);
pane.getChildren().add(valueMarker);
Community
  • 1
  • 1
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • Nice and clean object-oriented solution. However, I discovered a big performance problem. Every time a marker is updated the whole chart gets redrawn. So if you have a big chart and just want to change a marker position (e.g. a cursor running over the chart) the marker update might take very long (in my case nearly a second). Is there a more efficient solution for this special problem? Maybe with the StackPane? – Stefan Endrullis May 27 '15 at 08:39
  • @StefanEndrullis good point(didn't bother with performance :-) - did a StackPane improve the performance? Wondering though, why you are updating the marker frequently? – kleopatra Jun 15 '15 at 11:51
  • I use the marker to indicate the current position in a video related to the chart values. When the video is played the cursor (marker) has to be updated. However, I solved the performance by using a StackPane and by setting the cache attribute of the chart to true. See http://stackoverflow.com/questions/30482139/efficiently-updating-lines-markers-on-top-of-a-javafx-chart – Stefan Endrullis Jun 15 '15 at 12:10
1

Why don't you add that line into your chart? I have charts where I display the 100, 99, 95 and 50 percentiles, and the way I solve this is to add a line at the correct y-value for each percentile.

To do this, just add a line with two points, one at y=60 x=70 (the left-most x-axis value) and the other one at y=60 and x=120 (the right-most x-axis value).

The upside of this is that you do not have to align the horizontal line yourself manually, the downside being that this horizontal line will also be part of the legend. However, seeing as you do not have a legend that should be OK.

If you decide to add a legend, be sure to name the horizontal line appropriately, l

Joachim H. Skeie
  • 1,893
  • 17
  • 27
  • Thanks for your answer. If I get it right, the way you do it just works in a LineChart. My chart is a ScatterChart. Could you offer a piece of code if your example also works in a ScatterChart? – JoeHut Jun 16 '14 at 11:36
  • You should be able to add lines to any chart. I use these percentile lines in Area Charts, but AFAIK, you can add Series Data to any chart type. – Joachim H. Skeie Jun 16 '14 at 16:42
  • I think it's possible in an AreaChart, because the points of the Series Data get connected. But if I add a Series Data to a ScatterChart there is no connection between them. But maybe I don't get the point of your answer... Could you offer some code or a Screenshot of your chart? – JoeHut Jun 17 '14 at 14:21
  • I haven't tried this in a scatter chart myself, so your mileage might vary – Joachim H. Skeie Jun 18 '14 at 13:25