JavaFX Area Charts and adding dynamic labels following cursor - layout

I'd like to to be able to add a label to a chart that follows the cursor. I've already seen several examples of this on stack overflow already that attach labels to the values/nodes of a series but I want to be able to do this for the whole of the area chart. My problem comes when I want to add a label (ultimately a floating box of values). I'm struggling to figure out how I can do this and maintain the layout of the scene. When I add the chart to the scene as the dedicated item the chart renders as expected. But when I add a label to the scene it is shared and the chart no longer fills the entire canvas
Group root = new Group();
root.getChildren().addAll(sac,myLabel);
Scene scene = new Scene(root, 800, 600); // Now the Chart shares the scene with the label
I want to add the label so I can use it as the mouse moves over a chart and then simply translate it to the cursor position. I've no problem with the mouse events just that I can't seem to share a scene with objects that are transient. I believe this is down to a lack of understanding on how to manage this particular situation. It's entirely likely the solution to this problem is to dynamically generate this label and not have it as a static scene object. Any help gratefully received.
Full test case below (Java 8)
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedAreaChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class AreaChartTestCase extends Application {
final NumberAxis xAxis = new NumberAxis(1, 31, 1);
final NumberAxis yAxis = new NumberAxis();
final StackedAreaChart<Number, Number> sac
= new StackedAreaChart<Number, Number>(xAxis, yAxis);
Label myLabel = new Label();
#Override
public void start(Stage stage) {
stage.setTitle("Area Chart Sample");
sac.setTitle("Temperature Monitoring (in Degrees C)");
XYChart.Series<Number, Number> seriesApril
= new XYChart.Series<Number, Number>();
seriesApril.setName("April");
seriesApril.getData().add(new XYChart.Data(1, 4));
seriesApril.getData().add(new XYChart.Data(3, 10));
seriesApril.getData().add(new XYChart.Data(6, 15));
seriesApril.getData().add(new XYChart.Data(9, 8));
seriesApril.getData().add(new XYChart.Data(12, 5));
seriesApril.getData().add(new XYChart.Data(15, 18));
seriesApril.getData().add(new XYChart.Data(18, 15));
seriesApril.getData().add(new XYChart.Data(21, 13));
seriesApril.getData().add(new XYChart.Data(24, 19));
seriesApril.getData().add(new XYChart.Data(27, 21));
seriesApril.getData().add(new XYChart.Data(30, 21));
XYChart.Series<Number, Number> seriesMay
= new XYChart.Series<Number, Number>();
seriesMay.setName("May");
seriesMay.getData().add(new XYChart.Data(1, 20));
seriesMay.getData().add(new XYChart.Data(3, 15));
seriesMay.getData().add(new XYChart.Data(6, 13));
seriesMay.getData().add(new XYChart.Data(9, 12));
seriesMay.getData().add(new XYChart.Data(12, 14));
seriesMay.getData().add(new XYChart.Data(15, 18));
seriesMay.getData().add(new XYChart.Data(18, 25));
seriesMay.getData().add(new XYChart.Data(21, 25));
seriesMay.getData().add(new XYChart.Data(24, 23));
seriesMay.getData().add(new XYChart.Data(27, 26));
seriesMay.getData().add(new XYChart.Data(31, 26));
final Node chartPlotBackground = sac.lookup(".chart-plot-background");
chartPlotBackground.setOnMouseEntered((MouseEvent mouseEvent) -> {
myLabel.setVisible(true);
});
chartPlotBackground.setOnMouseExited((MouseEvent mouseEvent) -> {
myLabel.setVisible(false);
});
chartPlotBackground.setOnMouseMoved((MouseEvent mouseEvent) -> {
myLabel.setText("Location : X = " + mouseEvent.getSceneX() + " : Y = "+ mouseEvent.getSceneY());
myLabel.setTranslateX(mouseEvent.getSceneX());
myLabel.setTranslateY(mouseEvent.getSceneY());
});
Group root = new Group();
root.getChildren().addAll(sac, myLabel);
Scene scene = new Scene(root, 800, 600); // Now the Chart shares the scene with the label
sac.getData().addAll(seriesApril, seriesMay);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

So I think I've solved it. At least to a level that meets my requirements.
Group root = new Group();
root.setManaged(false);
root.getChildren().add(ac);
root.getChildren().add(myLabel);
Scene scene = new Scene(root);
scene.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneWidth, Number newSceneWidth) {
ac.setPrefWidth((double) newSceneWidth);
}
});
scene.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneHeight, Number newSceneHeight) {
ac.setPrefHeight((double) newSceneHeight);
}
});
By listening to changes in the width and height properties and manually managing the layout of the chart and label I was able to get the layout and functionality I required.I'm not 100% sure if this the optimal approach but it works.

Related

JavaFX Adding Context menu on Line Chart

I want to add a context menu on right click of my Line Chart.
Its basically for re-sizing the chart.Is there any way i can achieve this?
Here is a code snippet to bring up a resizing context menu on a line chart when the line chart is right clicked.
final MenuItem resizeItem = new MenuItem("Resize");
resizeItem.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
System.out.println("Resize requested");
}
});
final ContextMenu menu = new ContextMenu(
resizeItem
);
lineChart.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
if (MouseButton.SECONDARY.equals(event.getButton())) {
menu.show(stage, event.getScreenX(), event.getScreenY());
}
}
});
Some complete sample code:
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.control.*;
import javafx.scene.input.*;
import javafx.stage.Stage;
public class LineChartWithContextMenu extends Application {
#Override public void start(final Stage stage) {
stage.setTitle("Line Chart Sample");
//defining the axes
final NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
final NumberAxis yAxis = new NumberAxis();
//creating the chart
final LineChart<Number,Number> lineChart =
new LineChart<>(xAxis,yAxis);
lineChart.setTitle("Stock Monitoring, 2010");
//defining a series
XYChart.Series series = new XYChart.Series();
series.setName("My portfolio");
//populating the series with data
series.getData().setAll(
new XYChart.Data(1, 23),
new XYChart.Data(2, 14),
new XYChart.Data(3, 15),
new XYChart.Data(4, 24),
new XYChart.Data(5, 34),
new XYChart.Data(6, 36),
new XYChart.Data(7, 22),
new XYChart.Data(8, 45),
new XYChart.Data(9, 43),
new XYChart.Data(10, 17),
new XYChart.Data(11, 29),
new XYChart.Data(12, 25)
);
lineChart.getData().add(series);
//adding a context menu item to the chart
final MenuItem resizeItem = new MenuItem("Resize");
resizeItem.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
System.out.println("Resize requested");
}
});
final ContextMenu menu = new ContextMenu(
resizeItem
);
lineChart.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
if (MouseButton.SECONDARY.equals(event.getButton())) {
menu.show(stage, event.getScreenX(), event.getScreenY());
}
}
});
stage.setScene(
new Scene(
lineChart,800,600
)
);
stage.show();
}
public static void main(String[] args) { launch(args); }
}

How to restrict visibility of items?

Imagine that we have an AnchorPane, it has child Pane and there we have Button, for example.
I want this Button to be shown only inside this Pane.
In other words, it whould be cut by the Pane edges if it is not completely within Pane. Now the Button can be visible even if it is out of Pane rectangle.
this is what the clip of a node is made for.
Example:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ClipTest extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
StackPane pane = new StackPane();
pane.setMaxWidth(100);
pane.setMaxHeight(100);
pane.setLayoutX(50);
pane.setLayoutY(50);
Rectangle rect = new Rectangle(100, 100);
rect.setFill(null);
rect.setStroke(Color.RED);
Rectangle rect2 = new Rectangle(150, 150);
rect2.setFill(Color.BLUE);
pane.getChildren().addAll(rect2, rect);
root.getChildren().add(pane);
// Rectangle clip = new Rectangle(100, 100);
// clip.setLayoutX(25);
// clip.setLayoutY(25);
// pane.setClip(clip);
Scene scene = new Scene(root, 250, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
}
This produces:
Uncommenting the lines regarding the clip produces:
You can use clipping functionality to achieve this.
public class ClipPane extends Application {
#Override
public void start(Stage stage) throws Exception {
Pane clipPane = new Pane();
clipPane.setStyle("-fx-border-color: red;");
clipPane.setPrefSize(200, 200);
Rectangle rect = new Rectangle(200, 200);
clipPane.setClip(rect);
Button btn = new Button("Hello, world!");
btn.relocate(120, 0);
clipPane.getChildren().add(btn);
AnchorPane root = new AnchorPane();
root.getChildren().add(clipPane);
AnchorPane.setTopAnchor(clipPane, 50.);
AnchorPane.setLeftAnchor(clipPane, 50.);
stage.setScene(new Scene(root, 300, 300));
stage.show();
}
public static void main(String[] args) { launch(); }
}
Another approach, with using of observables. To clip items outside pane bounds (like css oveflow:hidden):
// create rectangle with sizes of pane,
// dont need to set x and y explictly
// as positions of clip node are relative to parent node
// (to pane in our case)
Rectangle clipRect = new Rectangle(pane.getWidth(), pane.getHeight());
// bind properties so height and width of rect
// changes according pane's width and height
clipRect.heightProperty().bind(pane.heightProperty());
clipRect.widthProperty().bind(pane.widthProperty());
// set rect as clip rect
pane.setClip(clipRect);

Set Stroke for BarChart

How can I set stroke (Color) for a BarChart? I always got yellow bars
Here is my code
import java.util.Set;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class StackPaneTest extends Application {
#Override
public void start(Stage primaryStage) {
final CategoryAxis xAxis1 = new CategoryAxis();
final NumberAxis yAxis1 = new NumberAxis();
final BarChart<String, Number> barChart = new BarChart<String, Number>(xAxis1, yAxis1);
barChart.setAlternativeRowFillVisible(false);
barChart.setLegendVisible(false);
barChart.setAnimated(false);
XYChart.Series serie1 = new XYChart.Series();
serie1.getData().add(new XYChart.Data("Jan", 1));
serie1.getData().add(new XYChart.Data("Feb", 2));
serie1.getData().add(new XYChart.Data("Mar", 1.5));
serie1.getData().add(new XYChart.Data("Apr", 3));
serie1.getData().add(new XYChart.Data("May", 2.5));
serie1.getData().add(new XYChart.Data("Jun", 5));
serie1.getData().add(new XYChart.Data("Jul", 4));
serie1.getData().add(new XYChart.Data("Aug", 8));
serie1.getData().add(new XYChart.Data("Sep", 6.5));
serie1.getData().add(new XYChart.Data("Oct", 13));
serie1.getData().add(new XYChart.Data("Nov", 10));
serie1.getData().add(new XYChart.Data("Dec", 20));
barChart.getData().addAll(serie1);
Set<Node> barNode = barChart.lookupAll(".default-color0.chart-bar");
for(final Node bar : barNode){
bar.setStyle("-fx-stroke: GREEN");
}
StackPane stack = new StackPane();
stack.getChildren().addAll(barChart);
Scene scene = new Scene(stack, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you want bar to be green, use setStyle("-fx-bar-fill: GREEN");
About stroke applying - it is a separate trouble, but it is not about style applying to the bar.
In common case, you may use caspian.css or modena.css files, to observe samples of style applying.

Imageview does not resize after ScaleTransition

I am trying to display three images in sequence with some scale transformation to each of them(images of 1,2,3). I am using ScaleTransition on single instance of imageview, after each successive transformation the imageView height get scaled which is not resetting
My Code is
public void start(Stage primaryStage) {
final IntegerProperty intProperty = new SimpleIntegerProperty(0);
final Image[] array = {
(new Image(WaitEffect.class.getResource("1.png")
.toExternalForm())),
(new Image(WaitEffect.class.getResource("2.png")
.toExternalForm())),
(new Image(WaitEffect.class.getResource("3.png")
.toExternalForm())) };
Group root = new Group();
Scene scene = new Scene(root, 400, 400, Color.BLACK);
StackPane pane = StackPaneBuilder.create()
.layoutX(scene.getWidth() / 2).layoutY(scene.getHeight() / 2)
.build();
final ImageView imageView = new ImageView();
pane.getChildren().add(imageView);
Timeline timeline = new Timeline();
int count = 0;
KeyValue value = new KeyValue(intProperty, count++);
KeyFrame frame = new KeyFrame(Duration.millis(2000),
new EventHandler<ActionEvent>() {
int co = 0;
#Override
public void handle(ActionEvent paramT) {
imageView.setImage(array[co]);
ScaleTransition st = ScaleTransitionBuilder.create()
.byX(2).byY(2).node(imageView)
.duration(Duration.millis(500)).build();
st.play();
imageView.setFitHeight(150);
imageView.setFitWidth(150);
imageView.resize(150, 150);
co++;
}
}, value);
timeline.getKeyFrames().add(frame);
timeline.setCycleCount(3);
timeline.play();
root.getChildren().addAll(pane);
primaryStage.setScene(scene);
primaryStage.show();
}`
ImageView doesn't reset it's properties after setting new Image. You need to reset it yourself, e.g. by setting from properties of ScaleTransition:
ScaleTransition st = ScaleTransitionBuilder.create()
.fromX(1).fromY(1)
.byX(2).byY(2).node(imageView)
.duration(Duration.millis(500)).build();

Updating lineChart in JavaFX

I am new to Java and JavaFX and have a question. I have an application with a single stage/scene in JavaFX. It has multiple tabs. In one of those tabs, I want the user to be able to input some values in text fields, press a submit button, and have a lineChart, on that same scene, be then displayed. I'm not having luck getting this to work.
An empty line Chart shows up when I run the app, but I cannot figure out how to get the series of data to appear.
Here is simple test code in the Controller class for the method that the code executes when the submit button is pressed:
#FXML
private void getEngDataPlot(ActionEvent event) {
lineChart.setTitle("Test charting");
//defining a series
XYChart.Series series = new XYChart.Series();
series.setName("My Test Data");
//populating the series with data
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);
}
must I 'do' something else in this method to get the chart on the scene to load?
I see show.stage(); in examples, but at the point I execute this method, my app is running and I already have the 'stage' shown - don't I? I'm obviously missing something fundamental to how this is supposed to hang together, but I don't know what.
thank you, and pardon my ignorance.
Most probably your chart axis are not set coorrectly so you can't see new data. Try to add next lines:
lineChart.getXAxis().setAutoRanging(true);
lineChart.getYAxis().setAutoRanging(true);
Or take a look at next example:
public class ChartUpdate extends Application {
#Override
public void start(Stage primaryStage) {
//random axis here
final LineChart lineChart = new LineChart(new NumberAxis(1, 22, 0.5), new NumberAxis(1, 22, 0.5));
lineChart.getXAxis().setAutoRanging(true);
lineChart.getYAxis().setAutoRanging(true);
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
lineChart.setTitle("Test charting");
//defining a series
XYChart.Series series = new XYChart.Series();
series.setName("My Test Data");
//populating the series with data
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);
}
});
VBox root = new VBox();
root.getChildren().addAll(btn, lineChart);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) { launch(); }
}
Maybe you already found the answer for your problem, but I belive this might help others in the future.
When creating a LineChart in Scene Builder, notice that there are two types of LineCharts: LineChart and LineChart (N x N). The first one has a category x-axis (where you can only define categories to show like a string). The second one has a number x-axis.
So if you want to display data vs. data, like in the example you show, you should use LineChart (N x N).
Hope it helps.

Resources