JavaFX Class LineChart zoom operation doesn't work - javafx

I have a problem I can not figure out. Please let me put the code first for better explanation:
public class TestXYChart extends Application {
NumberAxis xAxis = new NumberAxis(); // 1
NumberAxis yAxis = new NumberAxis(); // 2
XYChart.Series series1 = new XYChart.Series();
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
Scene scene = new Scene(root, 300, 250);
LineChart lineChart = new LineChart<Number, Number>(xAxis, yAxis);
root.setOnScroll(scrollHandler);
root.getChildren().addAll(lineChart);
series1.getData().add(new XYChart.Data(1, 20));
series1.getData().add(new XYChart.Data(2, 40));
series1.getData().add(new XYChart.Data(3, 100));
series1.getData().add(new XYChart.Data(4, 50));
series1.getData().add(new XYChart.Data(5, 170));
series1.getData().add(new XYChart.Data(6, 190));
series1.getData().add(new XYChart.Data(7, 30));
series1.getData().add(new XYChart.Data(8, 39));
series1.getData().add(new XYChart.Data(9, 44));
series1.getData().add(new XYChart.Data(10, 78));
series1.getData().add(new XYChart.Data(11, 93));
series1.getData().add(new XYChart.Data(12, 75));
// this.loadIntoDataBaseFromFile();
lineChart.getData().addAll(series1);
lineChart.setAnimated(true);
lineChart.setAlternativeColumnFillVisible(false);
lineChart.setAlternativeRowFillVisible(false);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
EventHandler scrollHandler = new EventHandler<ScrollEvent>(){
#Override
public void handle(ScrollEvent event) {
System.out.println("Handling Scroll Event");
xAxis.setLowerBound(xAxis.getLowerBound() + 1);
xAxis.setUpperBound(xAxis.getUpperBound() - 1);
}
};
}
I use the code above to test whether I can operate the LineChart, especially when I scroll mouse, the LineChart will zoom in/out.
I found the fact that exactly as above code, it doesn't work. But if we just change the first two line codes, change from:
NumberAxis xAxis = new NumberAxis(); // 1
NumberAxis yAxis = new NumberAxis(); // 2
to:
NumberAxis xAxis = new NumberAxis(0,12,0.5); // 1
NumberAxis yAxis = new NumberAxis(0,300,10); // 2
Everything works well. So, why it happens like this?Thank you.
And, if I seperate the new NumberAxis(...) instance from the declaration NumberAxis xAxis/yAxis, it doesn't work too.

The difference between using an empty constructor or with arguments is clear if you have a look at NumberAxis JavaDoc:
public NumberAxis()
Create a auto-ranging NumberAxis
and
public NumberAxis(double lowerBound, double upperBound, double tickUnit)
Create a non-auto-ranging NumberAxis with the given upper bound, lower bound and tick unit
Basically, the first one has autoRanging set to true, what means it will try to show the data, no matter how you modify the bounds of the axis.
On the contrary, the second one has autoRanging set to false, so any change in the axis bounds will be reflected on the chart.
Note that you can set this in the first case to change the default behaviour:
xAxis.setAutoRanging(false);

Related

JavaFX multi linechart and series - curves missing

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.

Why xAxis tickUnit didn't work in fxml file?

I want to create a scatter chart by fxml file.I put the code below in my fxml file but when it done the render,it did not like I expected.Do somebody know what's going on?
<BorderPane xmlns="http://javafx.com/javafx/8.0.121"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.ChartController">
<left>
<ScatterChart fx:id="memoryDistribution" prefWidth="360" title="Memory Distribution">
<xAxis>
<NumberAxis fx:id="xAxis" label="Memory Address/k" lowerBound="0" upperBound="7" tickUnit="1.00"/>
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" label="Memory Address/k" lowerBound="1" upperBound="257" tickUnit="8.00"/>
</yAxis>
</ScatterChart>
</left>
what above code do like this:
wrong expectation
But what I expect is like this:
expectation
the second image code:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Scatter Chart Sample");
final NumberAxis xAxis = new NumberAxis(0, 8, 1);
final NumberAxis yAxis = new NumberAxis(1, 257, 8);
final ScatterChart<Number,Number> sc = new
ScatterChart<>(xAxis,yAxis);
xAxis.setLabel("Memory Address/k");
yAxis.setLabel("Memory Address/k");
sc.setTitle("Memory Distribution");
XYChart.Series series1 = new XYChart.Series();
series1.setName("job-1");
series1.getData().add(new XYChart.Data(0.5, 5));
series1.getData().add(new XYChart.Data(1.5, 5));
series1.getData().add(new XYChart.Data(2.5, 5));
series1.getData().add(new XYChart.Data(3.5, 5));
series1.getData().add(new XYChart.Data(4.5, 5));
series1.getData().add(new XYChart.Data(5.5, 5));
series1.getData().add(new XYChart.Data(6.5, 5));
series1.getData().add(new XYChart.Data(7.5, 5));
series1.getData().add(new XYChart.Data(0.5, 13));
series1.getData().add(new XYChart.Data(1.5, 13));
series1.getData().add(new XYChart.Data(2.5, 13));
series1.getData().add(new XYChart.Data(3.5, 13));
series1.getData().add(new XYChart.Data(4.5, 13));
series1.getData().add(new XYChart.Data(5.5, 13));
XYChart.Series series2 = new XYChart.Series();
series2.setName("job-2");
series2.getData().add(new XYChart.Data(0.5, 29));
series2.getData().add(new XYChart.Data(1.5, 29));
series2.getData().add(new XYChart.Data(2.5, 29));
series2.getData().add(new XYChart.Data(3.5, 29));
series2.getData().add(new XYChart.Data(4.5, 29));
series2.getData().add(new XYChart.Data(5.5, 29));
sc.getData().addAll(series1, series2);
Scene chartScene = new Scene(sc,360,736);//best lookup
primaryStage.setScene(chartScene);
primaryStage.setMaximized(true);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I already done my demand:the grid on,and the cell size is 256.
I put an any kind of Pane in the fxml,when the init start it should be render and put a blank pane in there.
<center>
<ScrollPane fx:id="chartPane" prefWidth="380" prefHeight="760">
</ScrollPane>
</center>
Then I write the code to init a scatter chart and put it to the above pane.
In the class sample.ChartController as the fxml configured,put the init code in this class,name a method initialize.
private void initialize(){
final NumberAxis xAxis = new NumberAxis(0,8,1);
final NumberAxis yAxis = new NumberAxis(0,256,8);
xAxis.setLabel("Memory Address/k");
yAxis.setLabel("Memory Address/k");
this.memoryDistributionChart = new ScatterChart(xAxis,yAxis);
memoryDistributionChart.setTitle("Memory Distribution");
memoryDistributionChart.setPrefHeight(736);
memoryDistributionChart.setPrefWidth(360);
chartPane.setContent(memoryDistributionChart);
}
memoryDistributionChart is a ScatterChart,a member property.
chartPane is another member property.
finally,the result like this
Because when the main stage render,the controller initialize method should be invoked first.

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:

How do I change the size of a chart symbol in a javafx scatter chart?

I can change the symbol shape, colour, padding, radius but I can't seem to find a CSS item that will change the size of the symbol.
I had thought that if I recreated the symbol with smaller SVG path, that might be a way to do it, but it appears that my smaller shape just gets scaled up anyway.
I need smaller symbols but so far have not found a way to scale them.
Here is the code, and the loop where you can find all symbols and change Width and Height. I know only this way. If you try this in CSS you will change also legend under chart.
public class Chart extends Application {
#Override
public void start(Stage stage) {
//set Axis
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
//chart
final ScatterChart<Number, Number> lineChart = new ScatterChart<Number, Number>(xAxis, yAxis);
//prepare series
XYChart.Series series1 = new XYChart.Series();
series1.getData().add(new XYChart.Data(1, 2));
series1.getData().add(new XYChart.Data(2, 2));
series1.getData().add(new XYChart.Data(3, 1));
series1.getData().add(new XYChart.Data(3, 3));
series1.getData().add(new XYChart.Data(5, 2));
HBox hbox = new HBox();
//add series to chart
lineChart.getData().addAll(series1);
//take all series
for (XYChart.Series<Number, Number> series : lineChart.getData()) {
//for all series, take date, each data has Node (symbol) for representing point
for (XYChart.Data<Number, Number> data : series.getData()) {
// this node is StackPane
StackPane stackPane = (StackPane) data.getNode();
stackPane.setPrefWidth(50);
stackPane.setPrefHeight(50);
}
}
hbox.getChildren().addAll(lineChart);
Scene scene = new Scene(hbox, 800, 600);
scene.getStylesheets().add(getClass().getResource("/resources/chart.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Here is CSS wher you can set for this series radius
.default-color0.chart-symbol {
-fx-background-radius: 30px;
}
In this example symbols are bigger
This is how I created a rectangle in SVG path then changed the size in css.
.chart-symbol{
-fx-shape: "M 20.0 20.0 v24.0 h 10.0 v-24 Z";
-fx-padding: 7px 7px 7px 7px;
}

Zoom Bar Chart with mouse wheel

I found many examples how to zoom in charts but I'm looking for basic example in which the user can scroll with the mouse wheel.
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.stage.Stage;
public class BarChartSample extends Application {
final static String austria = "Austria";
final static String brazil = "Brazil";
final static String france = "France";
final static String italy = "Italy";
final static String usa = "USA";
#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<String,Number>(xAxis,yAxis);
bc.setTitle("Country Summary");
xAxis.setLabel("Country");
yAxis.setLabel("Value");
XYChart.Series series1 = new XYChart.Series();
series1.setName("2003");
series1.getData().add(new XYChart.Data(austria, 25601.34));
series1.getData().add(new XYChart.Data(brazil, 20148.82));
series1.getData().add(new XYChart.Data(france, 10000));
series1.getData().add(new XYChart.Data(italy, 35407.15));
series1.getData().add(new XYChart.Data(usa, 12000));
XYChart.Series series2 = new XYChart.Series();
series2.setName("2004");
series2.getData().add(new XYChart.Data(austria, 57401.85));
series2.getData().add(new XYChart.Data(brazil, 41941.19));
series2.getData().add(new XYChart.Data(france, 45263.37));
series2.getData().add(new XYChart.Data(italy, 117320.16));
series2.getData().add(new XYChart.Data(usa, 14845.27));
XYChart.Series series3 = new XYChart.Series();
series3.setName("2005");
series3.getData().add(new XYChart.Data(austria, 45000.65));
series3.getData().add(new XYChart.Data(brazil, 44835.76));
series3.getData().add(new XYChart.Data(france, 18722.18));
series3.getData().add(new XYChart.Data(italy, 17557.31));
series3.getData().add(new XYChart.Data(usa, 92633.68));
Scene scene = new Scene(bc,800,600);
bc.getData().addAll(series1, series2, series3);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Any help is welcome.
P.S I tested to insert the code into ScrollPane but I don't see any effect of using it.
ScrollPane s1 = new ScrollPane();
//s1.setPrefSize(620, 620);
s1.setHbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); // Horizontal scroll bar
s1.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); // Vertical scroll bar
s1.setContent(bc);
Take a look at this answer.
I've got it working with this code:
final double SCALE_DELTA = 1.1;
bc.setOnScroll(new EventHandler<ScrollEvent>() {
public void handle(ScrollEvent event) {
event.consume();
if (event.getDeltaY() == 0) {
return;
}
double scaleFactor = (event.getDeltaY() > 0) ? SCALE_DELTA : 1 / SCALE_DELTA;
bc.setScaleX(bc.getScaleX() * scaleFactor);
bc.setScaleY(bc.getScaleY() * scaleFactor);
}
});
bc.setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
if (event.getClickCount() == 2) {
bc.setScaleX(1.0);
bc.setScaleY(1.0);
}
}
});
You should check out Jason Winnebeck's JFXUtils. He has developed a really nice library for chart zooming as well as panning (dragging the zoomed chart area like Google Maps).
You can also easily reset the chart. I wired it up to double click like you suggested.
You can specify the format of the axis labels on his custom StableTicksAxis (I display dates and times on my x axis).
Zooming works from a click-and-drag on the chart area or on the axis for zooming in to the entire span of either the x or y axis.
Mouse wheel zooming is baked right in.
He has a convenience method for super simple default setup, which wraps the chart in the components required to implement zooming.

Resources