0

I want to add a Line between each Category on a CategoryAxis, the Problem is that the Categories change based on User Input, so i would need to add them at runtime, which I am doing by extending CustomBarChart and adding them on seriesAdded().

How can I get get the Position of each Category (xAxis) aswell as the Length of each Category (xAxis) so i can calculate where to put the Line between the Graphs.

Haven't found a way to get the Width of each Category yet.

Using the getDisplayPosition() should get me the Position but using it for this Graph e.g. gave me these results and I can't seem to figure out how they're related to the actual Position.

enter image description here

4.545454545454546
3.6363636363636367
2.7272727272727275
1.8181818181818188
0.9090909090909096
8.881784197001252E-16
-0.9090909090909083
-1.8181818181818175
-2.7272727272727266
-3.636363636363636
-4.545454545454544
Matze._
  • 77
  • 11
  • One hack to do this is to run a [lookup](https://openjfx.io/javadoc/17/javafx.graphics/javafx/scene/Node.html#lookupAll(java.lang.String)) on the chart [after it has been laid out](https://stackoverflow.com/questions/26152642/get-the-height-of-a-node-in-javafx-generate-a-layout-pass), then render a [PolyLine](https://openjfx.io/javadoc/17/javafx.graphics/javafx/scene/shape/Polyline.html) at the desired positions calculated from the looked up nodes. – jewelsea Mar 30 '22 at 00:04
  • Here is another technique (which is also a bit of a hack) and is based on [listeners for each data item's node property](https://stackoverflow.com/questions/15237192/how-to-display-bar-value-on-top-of-bar-javafx). That might (or might not) be applicable to your situation. – jewelsea Mar 30 '22 at 00:09
  • What Parameter would I use on lookup? Which Node exactly would i be looking for, the CategoryAxis Node? – Matze._ Mar 30 '22 at 00:37
  • You can try to adapt [this approach](https://stackoverflow.com/questions/38871202/how-to-add-shapes-on-javafx-linechart) – James_D Mar 30 '22 at 01:41
  • Use [ScenicView](https://github.com/JonathanGiles/scenic-view) to find nodes to lookup, or look in `modena.css` in your JavaFX installation for the css rules applied to nodes. For JavaFX charts in particular, there is a [css tutorial](https://docs.oracle.com/javafx/2/charts/css-styles.htm) which demonstrates the selectors used to find certain nodes. – jewelsea Mar 30 '22 at 05:55
  • the values look weird - I would suspect a usage error, so same procedure as always: [mcve] please ;) BTW: when playing with options, you might consider starting with a well-known example from a good tutorial (f.i. https://docs.oracle.com/javafx/2/charts/bar-chart.htm) to reduce complexity .. – kleopatra Mar 30 '22 at 12:12
  • forgot: using the example from the referenced tutorial, the values of categoryAxis.getDisplayPosition(category) look okay – kleopatra Mar 30 '22 at 12:22

1 Answers1

1

This is an adaptation of the idea here. It should provide a good starting point.

package org.jamesd.examples.barchartwithlines;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.BorderPane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

import java.io.IOException;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        CategoryAxis xAxis = new CategoryAxis();
        NumberAxis yAxis = new NumberAxis();
        BarChart<String, Number> chart = new BarChart<>(xAxis, yAxis) {
            private List<Line> lines = new ArrayList<>();
            @Override
            protected void layoutPlotChildren() {
                super.layoutPlotChildren();
                List<Series<String, Number>> data = getData();
                if (data.size() == 0) {
                    getPlotChildren().removeAll(lines);
                    lines.clear();
                    return ;
                }
                // assuming only one series
                Series<String, Number> series = data.get(0);
                int numLines = series.getData().size() - 1;
                // remove unused lines:
                for (int i = lines.size() - 1 ; i >= numLines ; i--) {
                    getPlotChildren().remove(lines.get(i));
                    lines.remove(i);
                }
                // create new lines:
                for (int i = lines.size() ; i < numLines ; i++) {
                    Line line = new Line();
                    lines.add(line);
                    getPlotChildren().add(line);
                }

                double[] catPositions = new double[numLines + 1];
                for (int i = 0 ; i < catPositions.length ; i++) {
                    Data<String, Number> d = series.getData().get(i);
                    catPositions[i] = xAxis.getDisplayPosition(d.getXValue());
                }
                Arrays.sort(catPositions);
                double startY = yAxis.getDisplayPosition(yAxis.getLowerBound());
                double endY = yAxis.getDisplayPosition(yAxis.getUpperBound());
                for (int i = 0 ; i < catPositions.length - 1 ; i++) {
                    Line line = lines.get(i);
                    double lineX = (catPositions[i] +  catPositions[i+1]) / 2 ;
                    line.setStartX(lineX);
                    line.setEndX(lineX);
                    line.setStartY(startY);
                    line.setEndY(endY);
                }
            }
        };

        Random rng = new Random();
        XYChart.Series<String, Number> series = new XYChart.Series<>();
        DateTimeFormatter format = DateTimeFormatter.ofPattern("LLL");
        for (Month month : Month.values()) {
            series.getData().add(new XYChart.Data<>(format.format(month), rng.nextDouble() * 100));
        }
        series.setName("2020");
        chart.getData().add(series);

        BorderPane root = new BorderPane(chart);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

This results in

enter image description here

James_D
  • 201,275
  • 16
  • 291
  • 322