JavaFX multi linechart and series - curves missing - javafx

I would like to plot one serie per chart, and in the end all the series together in one chart, but did not succeed. I'm asking for help. The code is simple and straight forward. Here is my code:
The main class;
public class TestChart extends Application {
GenPlots genPlots =new GenPlots();
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(event -> {
genPlots.GenPlots("Hello");
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 200, 250);
primaryStage.setTitle("TestCharts");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
And the class aimed to generate the series and the charts:
public class GenPlots {
public GenPlots() {};
Axis xAxis = new NumberAxis();
Axis yAxis = new NumberAxis();
LineChart<Number, Number> lineChart = new LineChart<Number, Number>
(xAxis, yAxis);
LineChart<Number, Number> lineChartMulti = new LineChart<Number,
Number>(xAxis, yAxis);
String serName="*";
// generate the linecharts
public void GenPlots (String hello) {
lineChart.getData().clear();
lineChartMulti.getData().clear();
for (int j=1; j<4;j++) {
XYChart.Series serSIF = new XYChart.Series();
serSIF=getSeries();
serName=String.valueOf(j);
serSIF.setName("Only one "+serName);
lineChart.getData().add(serSIF);
displayChart(lineChart,serName);
lineChartMulti.getData().add(serSIF);
}
displayChart(lineChartMulti,serName+"All Series");
} // end method
// get the series with values - sample
public XYChart.Series getSeries()
{
double x=0.0;
double fx=0.0;
XYChart.Series serL = new XYChart.Series();
for (int k=1; k<5;k++)
{
x=x+2;
fx=x*x*j;
serL.getData().add(new XYChart.Data(x,fx));
}
return serL;
}
// plot the lineCharts
public void displayChart( LineChart<Number, Number>lineChart, String
chartTitle )
{
Stage window = new Stage();
window.initModality(Modality.NONE);
StackPane vb = new StackPane();
vb.setPadding(new Insets(20, 20, 20, 20));
lineChart.setTitle(chartTitle);
vb.getChildren().add(lineChart);
Scene scene = new Scene(vb,500,600);
window.setScene(scene);
window.show();
}
}
Also, the last plots with all series are showing correctly, but the other ones , - one serie per chart - are distorted , or not plotted at all. It seems that the series are resetted to null each time a linechart is generated. I thinks is due to the that series are observable, but I can not figure out how to resolve this problem. Ask kindly for your contribution

I found the solution, which could be usefull for other people:
save the series in a ObservableList-
ObservableList<XYChart.Series<Number,Number>> ser = FXCollections.observableArrayList();
If needed do not clear the series itself, rather the ObservableList.

Related

JavaFX LineChart Mouse Hover Values [duplicate]

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);
}
}

Error in Tooltip for javaFX Linechart

I am using JavaFX LineChart. I am trying to set the tooltip on the linechart. I am getting error runtime error in event handle. What is wrong in the code?
public class ChartPlot
{
static LineChart <Number,Number> linechart;
static double[] xArray, yArray;
static ArrayList <Double> xList, yList;
public static XYChart.Series series;
public static LineChart linePlot(double[] x, double[] y) {
xArray = new double[x.length];
yArray = new double[y.length];
xArray = x;
yArray = y;
//Defining the x axis
final NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Wavelength");
//Defining the y axis
final NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Intensity");
//Creating the line chart
linechart = new LineChart <>(xAxis, yAxis);
linechart.getData().clear();
//Prepare XYChart.Series objects by setting data
series = new XYChart.Series();
//Setting the data to Line chart
for (int i = 0; i < xArray.length; i++) {
series.getData().add(new XYChart.Data(xArray[i], yArray[i]));
}
linechart.setCreateSymbols(false);
linechart.getData().add(series);
xAxis.setAutoRanging(true);
xAxis.setForceZeroInRange(false);
yAxis.setAutoRanging(true);
yAxis.setForceZeroInRange(false);
linechart.autosize();
linechart.applyCss();
String css = FXMLDocumentController.class.getResource("LSG.css").toExternalForm();
linechart.getStylesheets().add(css);
linechart.setLegendVisible(false);
for (XYChart.Series <Number, Number> s: linechart.getData()) {
for (XYChart.Data <Number, Number> d: s.getData()) {
Tooltip.install(d.getNode(), new Tooltip(
d.getXValue().toString() + "," + d.getYValue()));
//Adding Class on Hover or on click
d.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler < MouseEvent > () //----- This is the line I am getting error
{#Override
public void handle(MouseEvent event) {
System.out.println("X :" + d.getXValue() + " Y :" + d.getYValue());
}
});
}
}
return linechart;
}
}
I am using Open button to read a file and plot. While plotting I am trying to insert listener also to show the value on which I click.

How to make negative values in stacked bar chart start at previous stack?

I want to visualize incoming vs outgoing amounts per month in a stacked bar chart, but also the difference should be immediately visible.
I'm using the following sample code:
public class Statistics extends Application {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final StackedBarChart<String, Number> sbc = new StackedBarChart<>(xAxis, yAxis);
final XYChart.Series<String, Number> incoming = new XYChart.Series<>();
final XYChart.Series<String, Number> outgoing = new XYChart.Series<>();
#Override
public void start(Stage stage) {
xAxis.setLabel("Month");
xAxis.setCategories(FXCollections.observableArrayList(
Arrays.asList("Jan", "Feb", "Mar")));
yAxis.setLabel("Value");
incoming.setName("Incoming");
incoming.getData().add(new XYChart.Data("Jan", 25601.34));
incoming.getData().add(new XYChart.Data("Feb2", 20148.82));
incoming.getData().add(new XYChart.Data("Mar2", 10000));
outgoing.setName("Outgoing");
outgoing.getData().add(new XYChart.Data("Jan", -7401.85));
outgoing.getData().add(new XYChart.Data("Feb2", -1941.19));
outgoing.getData().add(new XYChart.Data("Mar2", -5263.37));
Scene scene = new Scene(sbc, 800, 600);
sbc.getData().addAll(incoming, outgoing);
stage.setScene(scene);
stage.show();
}
}
Which results in:
As you can see, the negative outgoing values are displayed below zero instead of being subtracted from the positive incoming values, which makes it hard to see the delta between the two.
What I want instead is that a bar for a negative value starts at the top of a bar for a positive value, but as they would overlap then, also apply an offset along the x-axis. At the example of the "Jan" series this should look similar to:
I was playing around with getNode().setTranslateX/Y(), but that does not seem to be a good solution as the translation units is not ticks in the chart, but something else.
How can I create a "stacked" bar chart like in the second image in an elegant way?
Your question is really cool!
So, here I want to share my solution using a BarChart instead of a StackedBarChart. It might be a bit hacky, but it is the only one that worked for me.
The first thing that came to my mind was to just simply take the bar and change its Y coordinate, but unfortunately we cannot access bars directly. So after digging through the source code of XYChart I found that all of the chart's content are located in Group plotContent, but the getter for that is protected. In this situation the one of things to do is to extend the class and increase the scope of method. So (finally), here the code comes:
public class Statistics extends Application {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
// here I use the modified version of chart
final ModifiedBarChart<String, Number> chart = new ModifiedBarChart<>(xAxis, yAxis);
final XYChart.Series<String, Number> incoming = new XYChart.Series<>();
final XYChart.Series<String, Number> outgoing = new XYChart.Series<>();
#Override
public void start(Stage stage) {
xAxis.setLabel("Month");
xAxis.setCategories(FXCollections.observableArrayList(
Arrays.asList("Jan", "Feb", "Mar")));
yAxis.setLabel("Value");
incoming.setName("Incoming");
incoming.getData().add(new XYChart.Data("Jan", 25601.34));
incoming.getData().add(new XYChart.Data("Feb", 20148.82));
incoming.getData().add(new XYChart.Data("Mar", 10000));
outgoing.setName("Outgoing");
// To set the min value of yAxis you can either set lowerBound to 0 or don't use negative numbers here
outgoing.getData().add(new XYChart.Data("Jan", -7401.85));
outgoing.getData().add(new XYChart.Data("Feb", -1941.19));
outgoing.getData().add(new XYChart.Data("Mar", -5263.37));
chart.getData().addAll(incoming, outgoing);
Scene scene = new Scene(chart, 800, 600);
stage.setScene(scene);
stage.show();
// and here I iterate over all plotChildren elements
// note, that firstly all positiveBars come, then all negative ones
int dataSize = incoming.getData().size();
for (int i = 0; i < dataSize; i++) {
Node positiveBar = chart.getPlotChildren().get(i);
// subtract 1 to make two bars y-axis aligned
chart.getPlotChildren().get(i + dataSize).setLayoutY(positiveBar.getLayoutY() - 1);
}
}
// note, that I extend BarChart here
private static class ModifiedBarChart<X, Y> extends BarChart<X, Y> {
public ModifiedBarChart(#NamedArg("xAxis") Axis<X> xAxis, #NamedArg("yAxis") Axis<Y> yAxis) {
super(xAxis, yAxis);
}
public ModifiedBarChart(#NamedArg("xAxis") Axis<X> xAxis, #NamedArg("yAxis") Axis<Y> yAxis, #NamedArg("data") ObservableList<Series<X, Y>> data) {
super(xAxis, yAxis, data);
}
public ModifiedBarChart(#NamedArg("xAxis") Axis<X> xAxis, #NamedArg("yAxis") Axis<Y> yAxis, #NamedArg("data") ObservableList<Series<X, Y>> data, #NamedArg("categoryGap") double categoryGap) {
super(xAxis, yAxis, data, categoryGap);
}
#Override
public ObservableList<Node> getPlotChildren() {
return super.getPlotChildren();
}
}
The result:

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();
}
}

What to do with StackedAreaChart rendering bug

I often encounter a really troublesome bug in StackedAreaChart. Instead of getting
I get
The bug seems to rear its head often when data-points-per-display-pixel density is high (though not extraordinarily), non-linear, and/or when successive data series' x-components are not aligned. For example, the two images above are the of the same window, simply resized.
Here is the code that reproduces the bug on Windows 7 32-bit and 64-bit platforms:
public class ChartTest extends Application {
#Override
public void start(Stage primaryStage) {
NumberAxis xAxis = new NumberAxis(0, 100, 10);
NumberAxis yAxis = new NumberAxis(0, 100, 10);
StackedAreaChart<Number,Number> chart;
chart = new StackedAreaChart(xAxis,yAxis);
chart.setLegendVisible(false);
chart.setCreateSymbols(false);
chart.getData().add(genSeries(0));
chart.getData().add(genSeries(1));
StackPane root = new StackPane();
root.getChildren().add(chart);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
private Series<Number,Number> genSeries(int a){
Series<Number,Number> s = new Series<>();
ObservableList<XYChart.Data<Number, Number>> d = s.getData();
for(int i=0; i<50; i++){
double x = Math.sqrt(2*i+a);
d.add(new XYChart.Data<>(x, i));
}
return s;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Does anyone know anything about this bug, how to work around it, or under what conditions exactly it appears?
It can get really bothersome:

Resources