Fix X and Y axis widths, JavaFX - javafx

How can I achieve fixed Axis width using JavaFx line graph.
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.stage.Stage;
public class Axis extends Application
{
private Timeline animation;
public static void main(final String[] args)
{
launch(args);
}
#Override
public void start(final Stage stage) throws Exception
{
final NumberAxis x = new NumberAxis();
final NumberAxis y = new NumberAxis();
x.setAutoRanging(false);
y.setAutoRanging(false);
final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(x, y);
// final Series series = new LineChart.Series<Number, Number>();
//
// series.getData().add(new XYChart.Data(20, 23));
// series.getData().add(new XYChart.Data(35, 14));
// series.getData().add(new XYChart.Data(43, 15));
// series.getData().add(new XYChart.Data(60, 24));
// series.getData().add(new XYChart.Data(10, 34));
//
// lineChart.getData().add(series);
lineChart.setPrefHeight(10);
lineChart.setPrefWidth(10);
lineChart.setMinHeight(10);
lineChart.setMinWidth(10);
lineChart.setMaxHeight(10);
lineChart.setMaxWidth(10);
final Scene scene = new Scene(lineChart, 800, 800);
stage.setScene(scene);
stage.show();
}
#Override
public void stop()
{
animation.pause();
}
}
I would like the grid (sample marked in red) not to scale when I resize the window. Currently the grid resizes when the window is resized as shown below
I am not able to achieve what I need using the minWidth and maxWidth API's

Preventing resizing of a Graph
You can prevent the graph resizing by setting it's preferred, min and max sizes:
lineChart.setMinSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
lineChart.setPrefSize(800, 600);
lineChart.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
The only exception to this is when the chart is the root of the scene, in which case you should wrap the chart in a Pane or Group to prevent it being resized to fit the scene (because of a layout quirk in the JavaFX 8 rendering logic).
Also note, wrapping any resizable node a Group will size the node (excluding transforms and effects) to it's preferred dimensions and never change them.
Unfortunately, setting the preferred size of the graph isn't exactly the same as setting the preferred sizes of the graph's axes directly, as it makes the entire chart that size, not a specific axis line, which is probably what you are looking for with your question.
Preventing resizing of a Graph Axis
I don't know how to do this.
You might think you could do the following:
xAxis.setMinWidth(Control.USE_PREF_SIZE);
xAxis.setPrefWidth(800);
xAxis.setMaxWidth(Control.USE_PREF_SIZE);
But this doesn't work in Java 8 when the axis is embedded inside a chart. This is because the XYChart layout routine ignores the preferred, min and max sizes of it's axes when it lays out the chart contents.
This is perhaps a bug, so you might want to log it as such at https://javafx-jira.kenai.com.

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 do I set the bounds of a Group so it shows a specific window of the many shapes inside, which may or may not exceed my intended bounds?

I have a JavaFX Group that contains a lot of ImageViews and a few overlays. I want it to render a specific mathematical rectangle which usually does not match the bounding box of the combined children. For a simpler concrete example I have created the following app:
import javafx.application.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.stage.*;
public class GroupWindow
extends Application
{
#Override
public void start(Stage stage)
throws Exception
{
Group gr = new Group();
Shape circle1 = new Circle(-300, 0, 50);
circle1.setFill(Color.GREEN);
gr.getChildren().add(circle1);
Shape circle2 = new Circle(0, 300, 50);
circle2.setFill(Color.GREEN);
gr.getChildren().add(circle2);
Shape LLrect = new Rectangle(-200, 0, 200,200);
LLrect.setFill(Color.BLUE);
gr.getChildren().add(LLrect);
VBox vbox = new VBox(gr);
stage.setScene(new Scene(vbox));
stage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
When I run the application I get a window that looks like
What I need is for it to look like
I could accomplish this if I knew the method call that would tell the Group to restrict its viewport to x in [-200..200] and y in [-200..200].
One comment suggests setting a clip. Adding a gr.setClip(new Rectangle(-200,-200, 400,400)); causes it to not draw the green circles, but the rendered window encompasses the same space as the first (undesirable) screenshot. So the clip does not affect how the Group decides its rendering window.
What technique should I use to specify this intent to javafx's layout engine?

Reload and display blank JavaFX BarChart after launching the program

I'm working on a JavaFX program that can display different grocery items as a BarChart. My code structure uses an initial loading class, GroceryGrapher.java, a controller, GroceryGrapherController.java, and an .fxml file, GroceryGrapher.fxml.
I'm relatively new to JavaFX and I've tried different approaches to creating and displaying my BarChart. For the most part I've resolved issues and can load the chart with its data and components, but my issue is that the chart won't display / update after the program has started. The BarChart is part of an AnchorPane (which all comes under a StackPane) in my FXML file, and I've tried to update it after initialization since it starts out blank but it remains blank.
The BarChart is part of the AnchorPane, which also has a MenuBar. My goal was to be able to update different parts of the BarChart via the MenuBar. When I load the program, the MenuBar and empty BarChart are loaded:
This is how I initialize my BarChart and its data:
public class GroceryGrapherController implements Initializable {
#FXML
private AnchorPane anchorPane = new AnchorPane();
#FXML
private NumberAxis xAxis = new NumberAxis();
#FXML
private CategoryAxis yAxis = new CategoryAxis();
#FXML
private BarChart<Number, String> groceryBarChart;
#FXML
//private Stage stage;
Parent root;
#Override
public void initialize(URL location, ResourceBundle resources) {
groceryBarChart = new BarChart<Number, String>(xAxis, yAxis);
groceryBarChart.setTitle("Horizontal Grocery List");
xAxis.setLabel("Number");
xAxis.setTickLabelRotation(90);
yAxis.setLabel("Grocery Type");
// Populate BarChart
XYChart.Series<Number, String> series1 = new XYChart.Series<>();
series1.setName("Chocolates");
series1.getData().add(new XYChart.Data<Number, String>(25601.34, "Reese"));
series1.getData().add(new XYChart.Data<Number, String>(20148.82, "Cadbury"));
series1.getData().add(new XYChart.Data<Number, String>(10000, "Mars"));
series1.getData().add(new XYChart.Data<Number, String>(35407.15, "Snickers"));
series1.getData().add(new XYChart.Data<Number, String>(12000, "Lindt"));
XYChart.Series<Number, String> series2 = new XYChart.Series<>();
series2.setName("Vegetables");
series2.getData().add(new XYChart.Data<Number, String>(57401.85, "Cabbage"));
series2.getData().add(new XYChart.Data<Number, String>(41941.19, "Lettuce"));
series2.getData().add(new XYChart.Data<Number, String>(45263.37, "Tomato"));
series2.getData().add(new XYChart.Data<Number, String>(117320.16, "Eggplant"));
series2.getData().add(new XYChart.Data<Number, String>(14845.27, "Beetroot"));
XYChart.Series<Number, String> series3 = new XYChart.Series<>();
series3.setName("Drinks");
series3.getData().add(new XYChart.Data<Number, String>(45000.65, "Water"));
series3.getData().add(new XYChart.Data<Number, String>(44835.76, "Coke"));
series3.getData().add(new XYChart.Data<Number, String>(18722.18, "Sprite"));
series3.getData().add(new XYChart.Data<Number, String>(17557.31, "Mountain Dew"));
series3.getData().add(new XYChart.Data<Number, String>(92633.68, "Beer"));
// Need to do this because of a bug with CategoryAxis... manually set categories
yAxis.setCategories(FXCollections.observableArrayList("Reese", "Cadbury", "Mars", "Snickers", "Lindt", "Cabbage", "Lettuce",
"Tomato", "Eggplant", "Beetroot", "Water", "Coke", "Sprite", "Mountain Dew", "Beer"));
groceryBarChart.getData().addAll(series1, series2, series3);
}
Now, I have an item:
In my MenuBar to load the BarChart (since it initially appears empty), which utilizes the a method I created, loadGraph. I've tried different methods to get my updated BarChart to show up with no major success. My initial idea was to use setScene in the loadGraph method to set the scene to the BarChart, as shown in this example.
Stage stage = (Stage) anchorPane.getScene().getWindow();
stage.setScene(new Scene(groceryBarChart, 800, 600));
While that did work, it naturally replaced the scene to solely consist of the BarChart, removing my MenuBar and previous GUI:
I saw this question which had a similar issue (I think) but I wasn't really sure how to apply it to my program. I got this code:
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("GroceryGrapher.fxml"));
root = (Parent) fxmlLoader.load();
GroceryGrapherController ggController = fxmlLoader.getController();
} catch (Exception e) {
e.printStackTrace();
}
I copied over my initialization from the initialize method to a new method and tried calling it with ggController but it didn't change anything. I also tried creating a new method, updateData:
#FXML
private void updateData() {
Platform.runLater(() -> {
groceryBarChart.setTitle("Horizontal Grocery List");
});
}
I tried calling it with ggController in the loadGraph method but my BarChart still didn't change.
I know I'm doing something wrong, but I'm not very familiar with the BarChart class and JavaFX in too much depth, so I figured I would come here to ask for help. I need to update the existing BarChart in my GUI so that my data can load without replacing the entire scene, in order for the MenuBar to remain visible in the scene able to perform functions. How would I go about altering the BarChart component part of my AnchorPane, updating the data as necessary, without completely replacing the scene as I did previously?
Thank you very much and sorry if this question is bad or my coding practices are wrong. All feedback would be much appreciated!
EDIT
Just in case it might help, here is my FXML code for the BarChart.
<BarChart fx:id="groceryBarChart" layoutY="31.0" prefHeight="566.0" prefWidth="802.0">
<xAxis>
<CategoryAxis fx:id="yAxis" side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" fx:id="xAxis" />
</yAxis>
</BarChart>
Most of the problems seem to be resolved, so I'll comment on this part only:
Ohh, I see. I increased the height and it seems to be thicker, thank you for your comment, that's very interesting behavior from the library. So I shouldn't use the approach I'm using?
Well, it depends. There is no other way to treat each data series as a separate list of categories in BarChart (at least, I'm not aware of any), so if you want to have groups of categories, each group with its own color, you'll have to use the same method as in your example.
The alternatives are to write your own implementation of bar chart layoutPlotChildren() method which will show only one bar per category (the only one if there are no two data series that contain the same category), or to find another chart library with those features. Of couse, you can always choose to show your data in different format (one data series for each month).
If you plan to use your original method, you'll have to adjust vertical position of bars (to center them again), and you'll have to fix bar height problem too:
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.chart.*;
import java.net.URL;
import java.util.ResourceBundle;
public class GroceryGrapherController implements Initializable {
#FXML
private NumberAxis xAxis;
#FXML
private CategoryAxis yAxis;
#FXML
private BarChart<Number, String> groceryBarChart;
//the height of one bar in the chart (if it can fit category area); you can pick another value
private final double barHeight = 10;
#Override
public void initialize(URL location, ResourceBundle resources) {
groceryBarChart.setTitle("Horizontal Grocery List");
//there's only one bar per category, so remove the gap (don't edit this, it's used in calculations)
groceryBarChart.setBarGap(0);
//gap between categories; you can pick another value
groceryBarChart.setCategoryGap(5);
//there're some bugs with the chart layout not updating when this is enabled, so disable it
groceryBarChart.setAnimated(false);
xAxis.setLabel("Number");
xAxis.setTickLabelRotation(90);
yAxis.setLabel("Grocery Type");
//if category height changes (ex. when you resize the chart), bar positions and height need to be recalculated
yAxis.categorySpacingProperty().addListener((observable, oldValue, newValue) ->
recalculateBarPositionsAndHeight()
);
//your original data
// Populate BarChart
final XYChart.Series<Number, String> series1 = new XYChart.Series<>();
series1.setName("Chocolates");
series1.getData().addAll(
new XYChart.Data<>(25601.34, "Reese"),
new XYChart.Data<>(20148.82, "Cadbury"),
new XYChart.Data<>(10000, "Mars"),
new XYChart.Data<>(35407.15, "Snickers"),
new XYChart.Data<>(12000, "Lindt")
);
final XYChart.Series<Number, String> series2 = new XYChart.Series<>();
series2.setName("Vegetables");
series2.getData().addAll(
new XYChart.Data<>(57401.85, "Cabbage"),
new XYChart.Data<>(41941.19, "Lettuce"),
new XYChart.Data<>(45263.37, "Tomato"),
new XYChart.Data<>(117320.16, "Eggplant"),
new XYChart.Data<>(14845.27, "Beetroot")
);
final XYChart.Series<Number, String> series3 = new XYChart.Series<>();
series3.setName("Drinks");
series3.getData().addAll(
new XYChart.Data<>(45000.65, "Water"),
new XYChart.Data<>(44835.76, "Coke"),
new XYChart.Data<>(18722.18, "Sprite"),
new XYChart.Data<>(17557.31, "Mountain Dew"),
new XYChart.Data<>(92633.68, "Beer")
);
groceryBarChart.getData().addAll(series1, series2, series3);
}
private void recalculateBarPositionsAndHeight() {
final int seriesCount = groceryBarChart.getData().size();
final double categoryHeight = yAxis.getCategorySpacing() - groceryBarChart.getCategoryGap();
final double originalBarHeight = categoryHeight / seriesCount;
final double barTranslateY = originalBarHeight * (seriesCount - 1) / 2;
//find the (bar) node associated with each series data and adjust its size and position
groceryBarChart.getData().forEach(numberStringSeries ->
numberStringSeries.getData().forEach(numberStringData -> {
Node node = numberStringData.getNode();
//calculate the vertical scaling of the node, so that its height matches "barHeight"
//or maximum height available if it's too big
double scaleY = Math.min(barHeight, categoryHeight) / originalBarHeight;
node.setScaleY(scaleY);
//translate the node vertically to center it around the category label
node.setTranslateY(barTranslateY);
})
);
}
}
I chose fixed bar height, but you don't need to (in which case you'll have to edit recalculateBarPositionsAndHeight method to use originalBarHeight).

javafx charts not working

I am using javafx charts for my project and after running my program I am getting this chart. what I want it to snap 3rd point first and then on to 4th point. Is there any way to do that. Thanks
I'm pretty sure that the 'AreaChart' that you're using plots data monotonically. So no matter what order the points get added to your series, the chart will plot them as monotonic.
note: Monotonic means that for every x value, there is a single y value.
If you want to break the 'monotonicness' of your plot and you're willing to give up the shaded area, you could construct it as a lineChart and then you would need to change your sorting policy. I've attached an example below to show you how to fix it.
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.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Pane root = new Pane();
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
LineChart chart = new LineChart(xAxis,yAxis);
XYChart.Series series = new XYChart.Series();
chart.getData().add(series);
chart.setAxisSortingPolicy(LineChart.SortingPolicy.NONE);
series.getData().add(new XYChart.Data(0,1));
series.getData().add(new XYChart.Data(1,2));
series.getData().add(new XYChart.Data(2,1));
series.getData().add(new XYChart.Data(1,0));
root.getChildren().add(chart);
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Unfortunately, if you're looking for both, it's going to come down to having to build your own chart class by extending from XYChart or maybe even AreaChart and defining your own custom AreaChart.

Scatter chart with connected dots javafx

I am learning javafx for creating GUI. I want to implement a simple line chart of X-Y NumberAxis. As per the property of line chart, the lines connecting the data points never intersect.
For example - Suppose for data points
series.getData().add(new XYChart.Data(1.5,1.5));
series.getData().add(new XYChart.Data(2.5,4.5));
series.getData().add(new XYChart.Data(4.5,2.5));
series.getData().add(new XYChart.Data(1.0,2.0));
series.getData().add(new XYChart.Data(3.0,3.0));`
The output for these points in line chart is -
]1)
where as it should be as the following edited image of scatter chart output of same points -
Is there a way to change this behavior of line chart? OR connect the dots of scatter chart?
Note that I am going to add points dynamically and lower bound, upper bound of both the axis are going to vary continuously.
Switch the sorting policy off for the chart.
lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.NONE);
When I use this, I get this beautiful child's scribble.
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 LineChartSample extends Application {
#Override public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number,Number> lineChart =
new LineChart<>(xAxis, yAxis);
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.getData().addAll(
new XYChart.Data<>(4, 24),
new XYChart.Data<>(1, 23),
new XYChart.Data<>(6, 36),
new XYChart.Data<>(8, 45),
new XYChart.Data<>(2, 14),
new XYChart.Data<>(9, 43),
new XYChart.Data<>(7, 22),
new XYChart.Data<>(12, 25),
new XYChart.Data<>(10, 17),
new XYChart.Data<>(5, 34),
new XYChart.Data<>(3, 15),
new XYChart.Data<>(11, 29)
);
lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.NONE);
Scene scene = new Scene(lineChart,800,600);
lineChart.getData().add(series);
lineChart.setLegendVisible(false);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Resources