JavaFX LineChart Mouse Hover Values [duplicate] - javafx

I am in the process of creating a line chart in JavaFX. All is good currently and it successfully creates the chart with the data I need from a database stored procedure. Anyway what I require if possible is for every data point on the LineChart to have a mouse hover event on it which states the value behind the specific point, for example £150,000. I have seen examples of this been done on PieCharts where it shows the % value on hover but I cannot find examples anywhere for LineCharts, can this even be done?
Can anyone point me in the right direction if possible?
Code so far:
private static final String MINIMIZED = "MINIMIZED";
private static final String MAXIMIZED = "MAXIMIZED";
private static String chartState = MINIMIZED;
// 12 Month Sales Chart
XYChart.Series<String, Number> series = new XYChart.Series<>();
XYChart.Series<String, Number> series2 = new XYChart.Series<>();
public void getDeltaData() {
try {
Connection con = DriverManager.getConnection(connectionUrl);
//Get all records from table
String SQL = "";
Statement stmt = con.createStatement();
//Create the result set from query execution.
ResultSet rs = stmt.executeQuery(SQL);
while (rs.next()) {
series.getData().add(new XYChart.Data<String, Number>(rs.getString(1),
Double.parseDouble(rs.getString(7))));
series2.getData().add(new XYChart.Data<String, Number>(rs.getString(1),
Double.parseDouble(rs.getString(8))));
}
rs.close();
stmt.close();
} catch (Exception e) {
}
yearChart = createChart();
}
protected LineChart<String, Number> createChart() {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
// setup chart
series.setName("Target");
series2.setName("Actual");
xAxis.setLabel("Period");
yAxis.setLabel("£");
yearChart.getData().add(series);
yearChart.getData().add(series2);
yearChart.setCreateSymbols(false);
return yearChart;
}
Answer provided by jewelsea is a perfect solution to this problem.
Thank you, jewelsea.

Use XYChart.Data.setNode(hoverPane) to display a custom node for each data point. Make the hoverNode a container like a StackPane. Add mouse event listeners so that you know when the mouse enters and leaves the node. On enter, place a Label for the value inside the hoverPane. On exit, remove the label from the hoverPane.
There is some example code to demonstrate this technique.
Output of the sample code is shown with the cursor hovered over the 22 node.

Using Tooltip:
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.control.Tooltip;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication250 extends Application
{
#Override
public void start(Stage stage)
{
stage.setTitle("Line Chart Sample");
//defining the axes
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
//creating the chart
final LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
lineChart.setTitle("Stock Monitoring, 2010");
//defining a series
XYChart.Series<Number, Number> series = new XYChart.Series();
series.setName("My portfolio");
//populating the series with data
Random rand = new Random();
TreeMap<Integer, Integer> data = new TreeMap();
//Create Chart data
for (int i = 0; i < 3; i++) {
data.put(rand.nextInt(51), rand.nextInt(51));
}
Set set = data.entrySet();
Iterator i = set.iterator();
while (i.hasNext()) {
Map.Entry me = (Map.Entry) i.next();
System.out.println(me.getKey() + " - " + me.getValue());
series.getData().add(new XYChart.Data(me.getKey(), me.getValue()));//Add data to series
}
lineChart.getData().add(series);
//loop through data and add tooltip
//THIS MUST BE DONE AFTER ADDING THE DATA TO THE CHART!
for (Data<Number, Number> entry : series.getData()) {
System.out.println("Entered!");
Tooltip t = new Tooltip(entry.getYValue().toString());
Tooltip.install(entry.getNode(), t);
}
Scene scene = new Scene(lineChart, 800, 600);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}

Related

Reduce number of tick labels on JavaFX xAxis

Up from a certain number of plotted ticks the labels on the xAxis are rotated by 90 degrees into a vertical position.
I would like to keep them horizontal and only plot as many as are fitting to the xAxis.
This shows how it looks and how I would like it to look:
Trying xAxis.setTickLabelRotation() is not showing any effect if there is not enough space to rotate the labels it seems.
I did a work around hiding the xAxis labels, positioning a HBox below the chart and fill it with a few labels that are horizontal. But that is no clean solution and did lead to other issues I would like to avoid.
This example:
JavaFX manually set NumberAxis ticks
shows how to subclass a NumberAxis but I did not make it yet to transfer that to achieve my goal. My idea is to subclass the NumberAxis, overwrite the methods that are responsible for plotting the labels on the xAxis and use a formatter that turns the numbers into the date format to be plotted.
Would that be the right approach and what would be the methods to address? I spent a lot of time going through the sources without success. Any hint into the right direction would be highly appreciated.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class LineChartSample extends Application {
#Override
public void start(Stage stage) {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis);
lineChart.setAnimated(false);
lineChart.setCreateSymbols(false);
XYChart.Series series = new XYChart.Series();
for (int i = 0; i < 100; i+=1)
series.getData().add(new XYChart.Data(msToDateString(i), Math.random()));
Scene scene = new Scene(lineChart, 800, 150);
lineChart.getData().add(series);
stage.setScene(scene);
stage.show();
}
//msToDateString-----------------------------------------------------------------
private static DateFormat df = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS");
private static Date result = new Date();
public static String msToDateString(long milliseconds) {
result.setTime(milliseconds);
return df.format(result);
}
public static void main(String[] args) {
launch(args);
}
}
I know this is an old topic now, but I came across this question while trying to deal with the similar problem myself. I was using custom LineChart class (extended) and had to override layoutPlotChildren() method to be able to put additional shapes to the plot list. To make graph more readable, I wanted to show every second tick on the X axis. In the end, I solved this problem by iterating tick marks and making every second mark invisible. Here is the snippet of the code:
public class MyLineChart extends LineChart<String, Double> {
public MyLineChart (
#NamedArg("xAxis") final Axis<String> xAxis,
#NamedArg("yAxis") final Axis<Double> yAxis) {
super(xAxis, yAxis);
}
...
#Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
...
ObservableList<Axis.TickMark<String>> tickMarks = getXAxis().getTickMarks();
for (int i = 0; i < tickMarks.size(); i++) {
tickMarks.get(i).setTextVisible(i % 2 == 0);
}
}
Hope this helps...

Javafx: How to hide XYChart Node when binding == null

I am binding a linechart to several TextFields, which contain double values as strings, or are empty. It works fine if the Field contains a number, but unfortunatey, I get an exception when it is empty.
I could find a way to handle "emptyness" and set it to 0.0, but actually I need to hide the node completey in this case (If "X" or "Y" Field is empty)
Any ideas how to solve it?
Basic Example with only one set of Textfields:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class LineChartSample extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("Demo");
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("X");
yAxis.setLabel("Y");
final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);
AnchorPane ap = new AnchorPane();
lineChart.setTitle("Demo");
XYChart.Series series = new XYChart.Series();
AnchorPane.setTopAnchor(lineChart, 5d);
AnchorPane.setLeftAnchor(lineChart, 5d);
AnchorPane.setRightAnchor(lineChart, 5d);
TextField t1 = new TextField("33.3");
TextField t2 = new TextField("33.3");
Data d = new XYChart.Data();
d.XValueProperty().bind(Bindings.when(t1.textProperty().isEmpty())
.then(0.0) // <-- here is the problem
.otherwise(Bindings.createDoubleBinding(() -> {
return Double.parseDouble(t1.getText());
}, t1.textProperty())));
d.YValueProperty().bind(Bindings.when(t2.textProperty().isEmpty())
.then(0.0) // <-- here is the problem
.otherwise(Bindings.createDoubleBinding(() -> {
return Double.parseDouble(t2.getText());
}, t2.textProperty())));
series.getData().add(d);
AnchorPane.setBottomAnchor(t1, 50d);
AnchorPane.setLeftAnchor(t1, 5d);
AnchorPane.setBottomAnchor(t2, 50d);
AnchorPane.setRightAnchor(t2, 5d);
ap.getChildren().addAll(lineChart, t1, t2);
Scene scene = new Scene(ap, 800, 600);
lineChart.getData().add(series);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I found a solution by adding
t1.textProperty().addListener((observable) -> {
if (t1.getText().isEmpty() || t2.getText().isEmpty()) {
d.getNode().setVisible(false);
} else {
d.getNode().setVisible(true);
}
});
t2.textProperty().addListener((observable) -> {
if (t1.getText().isEmpty() || t2.getText().isEmpty()) {
d.getNode().setVisible(false);
} else {
d.getNode().setVisible(true);
}
});
But if anybody knows a more elegant way, I would be quite happy.

Adding horizontal lines in javafx barchart [duplicate]

My desktop application has a timer for starting and stopping a test. On the graph, I want to create two vertical lines to indicate the start and stop time. "Adding vertical lines to StackPane with JavaFX" won't work for my case because I don't want the lines to stay at the same position and those lines should be drawn within the plot not the layout. When the user zooms on the chart, those vertical lines should move corresponding to where the user zooms. Thanks for any tip.
Here are my codes for creating the chart:
LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis, yAxis, dataset);
xAxis.setLabel("time(s)");
yAxis.setLabel("deg/s");
You need to extend the LineChart class and override the layoutPlotChildren method in order to show your markers.
Kleopatra did a very good example for a Scatter chart. The code below is a modified version for a line chart and has both vertical and horizontal markers:
public class LineChartSample extends Application {
#Override public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
final LineChartWithMarkers<Number,Number> lineChart = new LineChartWithMarkers<Number,Number>(xAxis,yAxis);
XYChart.Series series = new XYChart.Series();
series.setName("My portfolio");
series.getData().add(new XYChart.Data(1, 23));
series.getData().add(new XYChart.Data(2, 14));
series.getData().add(new XYChart.Data(3, 15));
series.getData().add(new XYChart.Data(4, 24));
series.getData().add(new XYChart.Data(5, 34));
series.getData().add(new XYChart.Data(6, 36));
series.getData().add(new XYChart.Data(7, 22));
series.getData().add(new XYChart.Data(8, 45));
series.getData().add(new XYChart.Data(9, 43));
series.getData().add(new XYChart.Data(10, 17));
series.getData().add(new XYChart.Data(11, 29));
series.getData().add(new XYChart.Data(12, 25));
lineChart.getData().add(series);
Data<Number, Number> horizontalMarker = new Data<>(0, 25);
lineChart.addHorizontalValueMarker(horizontalMarker);
Data<Number, Number> verticalMarker = new Data<>(10, 0);
lineChart.addVerticalValueMarker(verticalMarker);
Slider horizontalMarkerSlider = new Slider(yAxis.getLowerBound(), yAxis.getUpperBound(), 0);
horizontalMarkerSlider.setOrientation(Orientation.VERTICAL);
horizontalMarkerSlider.setShowTickLabels(true);
horizontalMarkerSlider.valueProperty().bindBidirectional(horizontalMarker.YValueProperty());
horizontalMarkerSlider.minProperty().bind(yAxis.lowerBoundProperty());
horizontalMarkerSlider.maxProperty().bind(yAxis.upperBoundProperty());
Slider verticalMarkerSlider = new Slider(xAxis.getLowerBound(), xAxis.getUpperBound(), 0);
verticalMarkerSlider.setOrientation(Orientation.HORIZONTAL);
verticalMarkerSlider.setShowTickLabels(true);
verticalMarkerSlider.valueProperty().bindBidirectional(verticalMarker.XValueProperty());
verticalMarkerSlider.minProperty().bind(xAxis.lowerBoundProperty());
verticalMarkerSlider.maxProperty().bind(xAxis.upperBoundProperty());
BorderPane borderPane = new BorderPane();
borderPane.setCenter( lineChart);
borderPane.setTop(verticalMarkerSlider);
borderPane.setRight(horizontalMarkerSlider);
Scene scene = new Scene(borderPane,800,600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private class LineChartWithMarkers<X,Y> extends LineChart {
private ObservableList<Data<X, Y>> horizontalMarkers;
private ObservableList<Data<X, Y>> verticalMarkers;
public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
super(xAxis, yAxis);
horizontalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()});
horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
verticalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
verticalMarkers.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);
}
public void addVerticalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (verticalMarkers.contains(marker)) return;
Line line = new Line();
marker.setNode(line );
getPlotChildren().add(line);
verticalMarkers.add(marker);
}
public void removeVerticalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
verticalMarkers.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();
}
for (Data<X, Y> verticalMarker : verticalMarkers) {
Line line = (Line) verticalMarker.getNode();
line.setStartX(getXAxis().getDisplayPosition(verticalMarker.getXValue()) + 0.5); // 0.5 for crispness
line.setEndX(line.getStartX());
line.setStartY(0d);
line.setEndY(getBoundsInLocal().getHeight());
line.toFront();
}
}
}
}
In order to add more marker lines, just use this:
Data<Number, Number> verticalMarker = new Data<>(10, 0);
lineChart.addVerticalValueMarker(verticalMarker);
Of course you could as well use a rectangle instead of a line like this:
private ObservableList<Data<X, X>> verticalRangeMarkers;
public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
...
verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()}); // 2nd type of the range is X type as well
verticalRangeMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}
public void addVerticalRangeMarker(Data<X, X> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (verticalRangeMarkers.contains(marker)) return;
Rectangle rectangle = new Rectangle(0,0,0,0);
rectangle.setStroke(Color.TRANSPARENT);
rectangle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.2));
marker.setNode( rectangle);
getPlotChildren().add(rectangle);
verticalRangeMarkers.add(marker);
}
public void removeVerticalRangeMarker(Data<X, X> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
verticalRangeMarkers.remove(marker);
}
protected void layoutPlotChildren() {
...
for (Data<X, X> verticalRangeMarker : verticalRangeMarkers) {
Rectangle rectangle = (Rectangle) verticalRangeMarker.getNode();
rectangle.setX( getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()) + 0.5); // 0.5 for crispness
rectangle.setWidth( getXAxis().getDisplayPosition(verticalRangeMarker.getYValue()) - getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()));
rectangle.setY(0d);
rectangle.setHeight(getBoundsInLocal().getHeight());
rectangle.toBack();
}
}
used like this:
Data<Number, Number> verticalRangeMarker = new Data<>(4, 10);
lineChart.addVerticalRangeMarker(verticalRangeMarker);
To make it look like a range:
I'm not sure which question you are referring to. You can basically do all this with some binding magic: the trick is to map the x value of the line to coordinates relative to the xAxis using xAxis.getDisplayPosition(...). Then you need to transform that coordinate to the coordinate relative to the container holding the chart and the line: the easiest way to do this is to first transform to Scene coordinates using xAxis.localToScene(...) and then to the coordinates of the container, using container.sceneToLocal(...).
Then you just need to let the binding observe everything that it needs to watch for changes: these will be the (numerical) bounds of the axes, the (graphical) bounds of the chart, and, if the line is going to move, a property representing its x-value.
Here is an SSCCE. In this example, I use a Slider to move the line around. I also make the line visible only if it's in range, and bind the y-coordinates so it spans the yAxis.
import java.util.Random;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ObservableDoubleValue;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class LineChartWithVerticalLine extends Application {
#Override
public void start(Stage primaryStage) {
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
chart.getData().add(createSeries());
Pane chartHolder = new Pane();
chartHolder.getChildren().add(chart);
DoubleProperty lineX = new SimpleDoubleProperty();
Slider slider = new Slider();
slider.minProperty().bind(xAxis.lowerBoundProperty());
slider.maxProperty().bind(xAxis.upperBoundProperty());
slider.setPadding(new Insets(20));
lineX.bind(slider.valueProperty());
chartHolder.getChildren().add(createVerticalLine(chart, xAxis, yAxis, chartHolder, lineX));
BorderPane root = new BorderPane(chartHolder, null, null, slider, null);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private Line createVerticalLine(XYChart<Number, Number> chart, NumberAxis xAxis, NumberAxis yAxis, Pane container, ObservableDoubleValue x) {
Line line = new Line();
line.startXProperty().bind(Bindings.createDoubleBinding(() -> {
double xInAxis = xAxis.getDisplayPosition(x.get());
Point2D pointInScene = xAxis.localToScene(xInAxis, 0);
double xInContainer = container.sceneToLocal(pointInScene).getX();
return xInContainer ;
},
x,
chart.boundsInParentProperty(),
xAxis.lowerBoundProperty(),
xAxis.upperBoundProperty()));
line.endXProperty().bind(line.startXProperty());
line.startYProperty().bind(Bindings.createDoubleBinding(() -> {
double lowerY = yAxis.getDisplayPosition(yAxis.getLowerBound());
Point2D pointInScene = yAxis.localToScene(0, lowerY);
double yInContainer = container.sceneToLocal(pointInScene).getY();
return yInContainer ;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty()));
line.endYProperty().bind(Bindings.createDoubleBinding(() -> {
double upperY = yAxis.getDisplayPosition(yAxis.getUpperBound());
Point2D pointInScene = yAxis.localToScene(0, upperY);
double yInContainer = container.sceneToLocal(pointInScene).getY();
return yInContainer ;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty()));
line.visibleProperty().bind(
Bindings.lessThan(x, xAxis.lowerBoundProperty())
.and(Bindings.greaterThan(x, xAxis.upperBoundProperty())).not());
return line ;
}
private Series<Number, Number> createSeries() {
Series<Number, Number> series = new Series<>();
series.setName("Data");
Random rng = new Random();
for (int i=0; i<=20; i++) {
series.getData().add(new Data<>(i, rng.nextInt(101)));
}
return series ;
}
public static void main(String[] args) {
launch(args);
}
}
I was able to create a drag and zoom in feature using the Line Chart Example mentioned here. The code listens to the mouse events and adds to the vertical ranges, which makes it appear to be dragging. JavaFX Drag and Zoom Line Chart Example
/**
* The ChartView.
*/
public class ChartController {
private ChartViewModel chartViewModel;
private CustomLineChart<Number, Number> lineChart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private XYChart.Series<Number, Number> series;
private List<Integer> data;
private boolean mouseDragged;
private double initialNumberStart;
private double initialNumberEnd;
#FXML
private VBox mainContainer;
#FXML
private HBox chartContainer;
/**
* The constructor.
*/
public ChartController() {
chartViewModel = new ChartViewModel();
mouseDragged = false;
}
/**
* The initialize method.
*/
public void initialize() {
createChart();
handleEvents();
}
/**
* Handles the events.
*/
private void handleEvents() {
lineChart.setOnMousePressed(pressed -> {
int minSize = 1;
// Get coordinate from the scene and transform to coordinates from the chart axis
Point2D firstSceneCoordinate = new Point2D(pressed.getSceneX(), pressed.getSceneY());
double firstX = xAxis.sceneToLocal(firstSceneCoordinate).getX();
lineChart.setOnMouseDragged(dragged -> {
mouseDragged = true;
Point2D draggedSceneCoordinate = new Point2D(dragged.getSceneX(), dragged.getSceneY());
double draggedX = xAxis.sceneToLocal(draggedSceneCoordinate).getX();
List<Double> numbers = filterSeries(firstX, draggedX);
int size = numbers.size();
double numberStart = size > minSize ? numbers.get(0) : initialNumberStart;
double numberEnd = numbers.size() > minSize ? numbers.get(size - 1) : initialNumberEnd;
if (size > minSize) {
lineChart.addVerticalRangeLines(new Data<>(numberStart, numberEnd));
}
lineChart.setOnMouseReleased(released -> {
if (mouseDragged) {
initialNumberStart = numberStart;
initialNumberEnd = numberEnd;
mouseDragged = false;
redrawChart();
}
});
});
});
}
/**
* Creates the charts.
*/
private void createChart() {
xAxis = new NumberAxis();
yAxis = new NumberAxis();
lineChart = new CustomLineChart<>(xAxis, yAxis);
data = chartViewModel.getData();
createSeries(data);
lineChart.getData().add(series);
initialNumberStart = 1;
initialNumberEnd = data.size() - 1;
chartContainer.getChildren().add(lineChart);
HBox.setHgrow(lineChart, Priority.ALWAYS);
}
/**
* Creates the series for the line chart.
*
* #param numbers The list of numbers for the series
*/
private void createSeries(List<Integer> numbers) {
int size = numbers.size();
series = new XYChart.Series<>();
series.setName("Example");
for (int i = 0; i < size; i++) {
series.getData().add(new XYChart.Data<Number, Number>(i, numbers.get(i)));
}
}
/**
* Filters the nodes and returns the node x positions within the firstX and lastX positions.
*
* #param firstX The first x position
* #param lastX The last x position
* #return The x positions for the nodes within the firstX and lastX
*/
private List<Double> filterSeries(double firstX, double lastX) {
List<Double> nodeXPositions = new ArrayList<>();
lineChart.getData().get(0).getData().forEach(node -> {
double nodeXPosition = lineChart.getXAxis().getDisplayPosition(node.getXValue());
if (nodeXPosition >= firstX && nodeXPosition <= lastX) {
nodeXPositions.add(Double.parseDouble(node.getXValue().toString()));
}
});
return nodeXPositions;
}
/**
* Updates the series for the chart.
*/
private void updateSeries() {
lineChart.getData().remove(0);
lineChart.getData().add(series);
}
/**
* Redraws the chart.
*/
private void redrawChart() {
List<Integer> filteredSeries = new ArrayList<>();
data.forEach(number -> {
if (number >= initialNumberStart && number <= initialNumberEnd) {
filteredSeries.add(number);
}
});
if (!filteredSeries.isEmpty()) {
createSeries(filteredSeries);
updateSeries();
lineChart.removeVerticalRangeLines();
}
}
/**
* Resets the series for the chart.
*
* #param event The event
*/
#FXML
void resetChart(ActionEvent event) {
createSeries(data);
updateSeries();
}
}

JavaFX - How to position the axis label to avoid conflicting with the tick marks

The below code is a simple line chart where the yAxis tick labels are wide in terms of their character length/width. The yAxis label positioned by default is placed too close to the yAxis, meaning the text conflicts with the tick marks. Is it possible to avoid this by increasing the gap between the label and the axis?
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class Example extends Application {
#Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis(0, 10, 2);
final NumberAxis yAxis = new NumberAxis(1.3378, 1.3390, 0.0001);
xAxis.setLabel("xAxis");
yAxis.setLabel("yAxis");
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis){
#Override public String toString(Number object){
return String.format("%1.4f", object); }
});
final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);
XYChart.Series series = new XYChart.Series();
final XYChart.Data d1 = new XYChart.Data(0.0,1.3379);
final XYChart.Data d2 = new XYChart.Data(2.0,1.3387);
final XYChart.Data d3 = new XYChart.Data(2.5,1.3385);
final XYChart.Data d4 = new XYChart.Data(3.5,1.3387);
final XYChart.Data d5 = new XYChart.Data(8.0,1.3378);
final XYChart.Data d6 = new XYChart.Data(9.5,1.3388);
series.getData().addAll(d1, d2, d3, d4, d5, d6);
lineChart.getData().add(series);
lineChart.setLegendVisible(false);
final Scene scene = new Scene(lineChart, 400, 300);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I don't think it is possible to relocate "yAxis" label or increase the gap, but you may want to use the following workaround to avoid the text conficts:
lineChart.getYAxis().setTickLabelRotation(-90);
And you will get the following chart:
It is possible to locate the label of an axis.
For Example to move the Label 10 Pixel to the left, just use:
lineChart.getYAxis()
.lookup(".axis-label")
.setStyle("-fx-label-padding: -10 0 0 0;");

How to create BarChar that contain in each series one element (Bar) in javafx

I created a Bar char but there is a big gap between the bars (I think it's because I use one element in each series). I tried setBarGap() but it doesn't change anything. here is what i do : http://i.imgur.com/k3ZEYCT.png? and here is an example of what i want : http://i.imgur.com/fb7tMx5.png
ObservableList<XYChart.Series<String, Number>> answer = FXCollections.observableArrayList();
for(int i=1;i<9;i++){
....
...
...
XYChart.Series<String, Number> series6 = new XYChart.Series<String, Number>();
series6.getData().add(new XYChart.Data(String.valueOf(i), 2*i));
answer.add(series6);
}
XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
series1.getData().add(new Data<String, Number>(">8",rs2.getInt(1)));
answer.add(series1);
return answer;
The problem is you add more than one category for all data series. If one data series is named "1", and the other is named "2" and these are added to different series then the layout needs to save room in each category's group of bars for the missing category. Like there's a space in "2" group for "1" bars, but you haven't added any.
Make sure you never add more than one category - they all have to have the same value. Since it seems you're using different series for the color and legend, then just don't name the categories.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Side;
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.stage.Stage;
public class BarChartSample extends Application {
#Override public void start(Stage stage) {
stage.setTitle("Bar Chart Sample");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final BarChart<String,Number> bc = new BarChart(xAxis,yAxis);
bc.setTitle("Frequency of visit");
bc.setLegendSide(Side.RIGHT);
//if you want axis labels
xAxis.setLabel("");
yAxis.setLabel("");
bc.getData().addAll(makeSerie("daily",20),
makeSerie("twice a week",30),
makeSerie("weekly",40),
makeSerie("monthly",50));
xAxis.setVisible(false);
xAxis.setTickMarkVisible(false);
xAxis.setTickLabelsVisible(false);
bc.setBarGap(20);
Scene scene = new Scene(bc);
stage.setScene(scene);
stage.show();
}
//just to save typing
private XYChart.Series<String, Number> makeSerie(String name, Number value){
return new XYChart.Series(name,
FXCollections.observableArrayList(new XYChart.Data("", value)));
}
public static void main(String[] args) {
launch(args);
}
}

Resources