JavaFX plotting LinexChart - javafx

I am trying to learn how plot graph using JavaFX and there is my code where I trying to draw the very simple sin series:
public class Controller implements Initializable {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
#FXML
private LineChart<Number, Number> ExactChart = new LineChart<Number, Number>(xAxis, yAxis);
private Series sin_series = new Series();
#FXML
private void plotTheChart(ActionEvent event){
int N = 100;
double x0 = -Math.PI;
double X = Math.PI;
double h = (X-x0)/(N);
double[] x = new double[N];
double[] y = new double[N];
x[0] = x0;
for(int i = 1; i < N; i++){
x[i] = x[i-1] + h;
}
for(int i = 0; i < N; i++){
y[i] = Math.sin(x[i]);
}
sin_series.setName("sin");
for(int i = 0; i < N; i++){
sin_series.getData().add(new Data(x[i], y[i]));
}
ExactChart.getData().addAll(sin_series);
}
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
}
}
But when I execute it I face following problem:
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.String (java.lang.Double and java.lang.String are in module java.base of loader 'bootstrap')
I do not see any part of the code where I am castin double to string. Can you show me where I am wrong?

Related

Zoom linecharts with date on axis

I am looking for some class/tool in javafx for zooming line chart.
The problem is that i am using date format on xAxis.
I have found some solutions, there are below:
https://gist.github.com/james-d/7252698
https://github.com/kerner1000/javafx-chart-zooming
https://www.programcreek.com/java-api-examples/index.php?source_dir=fancy-chart-master/src/de/tesis/dynaware/javafx/fancychart/zoom/Zoom.java
These solutions use NumberAxis and I tried integrate date format in them, but i didn't get expected result.
Here is my code:
private void setUpZooming(final Rectangle rect, final Node zoomingNode, int min, int max, int step) {
final ObjectProperty<Point2D> mouseAnchor = new SimpleObjectProperty<>();
zoomingNode.setOnMousePressed(event -> {
mouseAnchor.set(new Point2D(event.getX(), event.getY()));
rect.setWidth(0);
rect.setHeight(0);
});
zoomingNode.setOnMouseDragged(event -> {
double x = event.getX();
double y = event.getY();
rect.setX(Math.min(x, mouseAnchor.get().getX()));
rect.setY(Math.min(y, mouseAnchor.get().getY()));
rect.setWidth(Math.abs(x - mouseAnchor.get().getX()));
rect.setHeight(Math.abs(y - mouseAnchor.get().getY()));
});
zoomingNode.setOnMouseReleased(event -> {
if(rect.getHeight() >= 10 && rect.getWidth() >= 10){
doZoom(rect, (LineChart<Number, Number>) zoomingNode);
}
});
zoomingNode.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
if (e.isSecondaryButtonDown()) {
LineChart<Number, Number> lineChart = (LineChart<Number, Number>) zoomingNode;
final NumberAxis xAxis = (NumberAxis) lineChart.getXAxis();
final NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
yAxis.setLowerBound(min);
yAxis.setUpperBound(max);
rect.setWidth(0);
rect.setHeight(0);
}
});
}
private void doZoom(Rectangle zoomRect, LineChart<Number, Number> chart) {
Point2D zoomTopLeft = new Point2D(zoomRect.getX(), zoomRect.getY());
Point2D zoomBottomRight = new Point2D(zoomRect.getX() + zoomRect.getWidth(), zoomRect.getY() + zoomRect.getHeight());
final NumberAxis yAxis = (NumberAxis) chart.getYAxis();
Point2D yAxisInScene = yAxis.localToScene(0, 0);
final NumberAxis xAxis = (NumberAxis) chart.getXAxis();
Point2D xAxisInScene = xAxis.localToScene(0, 0);
yAxis.setAutoRanging(false);
xAxis.setAutoRanging(false);
double xOffset = zoomTopLeft.getX() - yAxisInScene.getX();
double yOffset = zoomBottomRight.getY() - xAxisInScene.getY();
double yAxisScale = yAxis.getScale();
double xAxisScale = xAxis.getScale();
xAxis.setLowerBound(xAxis.getLowerBound() + xOffset / xAxisScale);
xAxis.setUpperBound(xAxis.getLowerBound() + zoomRect.getWidth() / xAxisScale);
yAxis.setLowerBound(yAxis.getLowerBound() + yOffset / yAxisScale);
yAxis.setUpperBound(yAxis.getUpperBound() + zoomRect.getHeight() / yAxisScale);
zoomRect.setWidth(0);
zoomRect.setHeight(0);
}

How to mix ScatterChart with horizontal and vertical lines

I'm trying to create vertical and horizontal lines on ScatterChart together with points. I cannot find a way to mix them up.
This is the code I generate points on chart.
vbox {
add(ScatterChart(NumberAxis(), NumberAxis()).apply {
val seriesMap: HashMap<String, XYChart.Series<Number, Number>> = HashMap()
pointsList
.map { it.decisionClass }
.distinct()
.forEach {
seriesMap.put(it, XYChart.Series())
}
for (point in pointsList) {
seriesMap.get(point.decisionClass)?.data(point.axisesValues[0], point.axisesValues[1])
}
seriesMap
.toSortedMap()
.forEach { key, value ->
value.name = key
data.add(value)
}
(xAxis as NumberAxis).setForceZeroInRange(false)
(yAxis as NumberAxis).setForceZeroInRange(false)
})
}
I don't know Kotlin, so this answer is in Java. I think you can probably translate it to Kotlin (and feel free to post another answer if so).
To add additional nodes to a chart, there are three things you need:
Call getPlotChildren() and add the new nodes to the chart's "plot children"
Override the layoutPlotChildren() method to update the positions of your nodes when the chart is laid out
Use getDisplayPosition(...), defined in Axis, to get the location in the coordinate system of the plot area of the chart from a value on the axis.
The following SSCCE creates a scatter chart somewhat similar to the one you posted in the screen shot, and adds lines gating a specified series (i.e. the line on the left extends the height of the chart and passes through the minimum x-value of the series; the line at the top extends the width of the chart, and passes through the maximum y-value of the series, etc). I added radio buttons so you can choose which series is "bounded" by the lines.
import java.util.Random;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class ScatterChartWithLines extends Application {
private final class ScatterChartWithBoundary extends ScatterChart<Number, Number> {
private Series<Number, Number> boundedSeries ;
private final NumberAxis yAxis;
private final NumberAxis xAxis;
private final Line leftLine = new Line();
private final Line rightLine = new Line();
private final Line topLine = new Line();
private final Line bottomLine = new Line();
{
getPlotChildren().addAll(leftLine, rightLine, topLine, bottomLine);
}
private ScatterChartWithBoundary(NumberAxis xAxis, NumberAxis yAxis) {
super(xAxis, yAxis);
this.yAxis = yAxis;
this.xAxis = xAxis;
}
#Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
getPlotChildren().removeAll(leftLine, rightLine, topLine, bottomLine);
if (boundedSeries != null) {
getPlotChildren().addAll(leftLine, rightLine, topLine, bottomLine);
double minX = Double.MAX_VALUE ;
double minY = Double.MAX_VALUE ;
double maxX = Double.MIN_VALUE ;
double maxY = Double.MIN_VALUE ;
for (Data<Number, Number> d : boundedSeries.getData()) {
if (d.getXValue().doubleValue() < minX) minX = d.getXValue().doubleValue() ;
if (d.getXValue().doubleValue() > maxX) maxX = d.getXValue().doubleValue() ;
if (d.getYValue().doubleValue() < minY) minY = d.getYValue().doubleValue() ;
if (d.getYValue().doubleValue() > maxY) maxY = d.getYValue().doubleValue() ;
}
positionLineInAxisCoordinates(leftLine, minX, yAxis.getLowerBound(), minX, yAxis.getUpperBound());
positionLineInAxisCoordinates(rightLine, maxX, yAxis.getLowerBound(), maxX, yAxis.getUpperBound());
positionLineInAxisCoordinates(bottomLine, xAxis.getLowerBound(), minY, xAxis.getUpperBound(), minY);
positionLineInAxisCoordinates(topLine, xAxis.getLowerBound(), maxY, xAxis.getUpperBound(), maxY);
}
}
private void positionLineInAxisCoordinates(Line line, double startX, double startY, double endX, double endY) {
double x0 = xAxis.getDisplayPosition(startX);
double x1 = xAxis.getDisplayPosition(endX);
double y0 = yAxis.getDisplayPosition(startY);
double y1 = yAxis.getDisplayPosition(endY);
line.setStartX(x0);
line.setStartY(y0);
line.setEndX(x1);
line.setEndY(y1);
}
public void setBoundedSeries(Series<Number, Number> boundedSeries) {
if (! getData().contains(boundedSeries)) {
throw new IllegalArgumentException("Specified series is not displayed in this chart");
}
this.boundedSeries = boundedSeries ;
requestChartLayout();
}
}
private final Random rng = new Random();
#Override
public void start(Stage primaryStage) {
Series<Number, Number> series1 = new Series<>("Series 1", FXCollections.observableArrayList());
Series<Number, Number> series2 = new Series<>("Series 2", FXCollections.observableArrayList());
Series<Number, Number> series3 = new Series<>("Series 3", FXCollections.observableArrayList());
for (int i = 0 ; i < 40 ; i++) {
series1.getData().add(new Data<>(rng.nextDouble()*2 + 4, rng.nextDouble()*3 + 2));
series2.getData().add(new Data<>(rng.nextDouble()*2.5 + 4.75, rng.nextDouble()*1.5 + 2));
series3.getData().add(new Data<>(rng.nextDouble()*3 + 5, rng.nextDouble()*1.5 + 2.75));
}
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
xAxis.setForceZeroInRange(false);
yAxis.setForceZeroInRange(false);
ScatterChartWithBoundary chart = new ScatterChartWithBoundary(xAxis, yAxis);
chart.getData().addAll(series1, series2, series3);
VBox buttons = new VBox(2);
ToggleGroup toggleGroup = new ToggleGroup();
for (Series<Number, Number> series : chart.getData()) {
RadioButton rb = new RadioButton(series.getName());
rb.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected) {
chart.setBoundedSeries(series);
}
});
rb.setToggleGroup(toggleGroup);
buttons.getChildren().add(rb);
}
BorderPane root = new BorderPane(chart);
root.setTop(buttons);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Here is a typical result:

How to connect two points in javaFX in two-axis chart

In JavaFX line chart, a type of two-axis chart that presents data as a series of points connected by straight lines. All points are connected with one line. But I want to connect them with my order . Like there are three points p1(x1,y1), p2(x2,y2), p3(x3,y3) . In line chart, they are all connected with a single line , but I want connect p1,p3 and p1,p2 separately . How can I do that ? X-axis and Y-axis must present there .
By default JavaFX will join all points in a series. Simply solution is to use multiple series. Note you cannot share Data instances between series because Data stores a back-reference to the Node - see javafx.scene.chart.XYChart.Data.getNode()
#Override
public void start(Stage primaryStage) throws Exception {
NumberAxis xAxis1 = new NumberAxis();
NumberAxis yAxis1 = new NumberAxis();
LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis1, yAxis1);
double p1x = 0;
double p1y = 0;
double p2x = 5;
double p2y = 5;
double p3x = 3;
double p3y = 5;
Series<Number, Number> series1 = new Series<>();
series1.getData().add(new Data<>(p1x, p1y));
series1.getData().add(new Data<>(p2x, p2y));
chart.getData().add(series1);
Series<Number, Number> series2 = new Series<>();
series2.getData().add(new Data<>(p2x, p2y));
series2.getData().add(new Data<>(p3x, p3y));
chart.getData().add(series2);
primaryStage.setScene(new Scene(chart));
primaryStage.show();
}
I improved on #Adam answer to address your new probem:
#Override
public void start(Stage primaryStage) throws Exception {
NumberAxis xAxis1 = new NumberAxis();
NumberAxis yAxis1 = new NumberAxis();
LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis1, yAxis1);
Random random = new Random();
ArrayList<Series<Number, Number>> seriesContainer = new ArrayList();
for(int i = 0; i < 10; i++)
{
Series<Number, Number> series1 = new Series<>();
series1.getData().add(new XYChart.Data<>(random.nextDouble() * 100, random.nextDouble() * 100));
series1.getData().add(new XYChart.Data<>(random.nextDouble() * 100, random.nextDouble() * 100));
seriesContainer.add(series1);
}
chart.getData().addAll(seriesContainer);
primaryStage.setScene(new Scene(chart));
primaryStage.show();
}

Put XAxis value on top of bar chart with diagonally in javafx

I have created a bar chart with multi series as you can see at the picture.
The List ent has 2 Lists.
I need to somehow write their xAxis value on top of their body diagonally (with a rotation of 45 degree). And set another value for xAxis later.
public class FunctionalRedundantController {
#FXML
private BarChart<String, Number> barchart;
#FXML
private CategoryAxis funcNameAxis;
#FXML
private NumberAxis yAxis;
private ObservableList<String> funcNames = FXCollections
.observableArrayList();
private DataConstructor dc = new DataConstructor();
#FXML
private void initialize() {
funcNameAxis.setLabel("Name of Functions");
funcNameAxis.tickLabelFontProperty().set(Font.font(10));
yAxis.setLabel("Redundant");
}
public void setfunctionalredundant() {
XYChart.Series<String, Number> series = new XYChart.Series<>();
XYChart.Series<String, Number> series2 = new XYChart.Series<>();
int green = 0;
int yellow = 0;
List<String> compGreen = new ArrayList<String>();
List<String> compyellow = new ArrayList<String>();
for (List<String> ent : dc.getFuncTypeOrg().values()) {
if (ent.size() > 10) {
for (int k = 0; k < ent.size(); k++) {
if (ent.get(k).equals("hasType")) {
if (ent.get(k+1).equals("Green")) {
series.getData().add(
new XYChart.Data<String, Number>(ent.get(k-2), 1));
}
else if(ent.get(k+1).equals("yellow")){
series2.getData().add(
new XYChart.Data<String, Number>(ent.get(k-2), 1));
}
}
}
}
}
barchart.getYAxis().setVisible(false);
barchart.getYAxis().setOpacity(0);
barchart.getData().addAll(series, series2);
barchart.setBarGap(1);
barchart.setCategoryGap(20);
}
}

Loan calculator using tableview in JavaFX

I am attempting to make a loan calculator that shows payment number, interest, principal, and balance in tableview in JavaFX. Right now only the principal and balance columns are populating. I am fairly new to JavaFX, so I am not sure if I have made a mistake in FX or in my formula. Here is my code:
public class LoanTable extends Application {
Integer payment;
Double interest;
Double principal;
Double balance;
#Override
public void start(Stage primaryStage) {
BorderPane border = new BorderPane();
GridPane grid = new GridPane();
ScrollPane scroll = new ScrollPane();
Label labelTitle = new Label("Enter Loan Amount, Number of Years, and Annual Interest Rate");
labelTitle.getStyleClass().add("labelTitle");
Label loan = new Label("Loan Amount");
TextField loanAmount = new TextField();
Label years = new Label("Number of Years");
TextField numYears = new TextField();
Label interestRate = new Label("Annual Interest Rate");
TextField rate = new TextField();
Button compute = new Button("Display Loan Schedule");
TableView<Finance> table = new TableView<>();
TableColumn paymentColumn = new TableColumn("Payment#");
paymentColumn.setMinWidth(200);
paymentColumn.setCellValueFactory(new PropertyValueFactory<Finance, Integer>("payment"));
TableColumn interestColumn = new TableColumn("Interest");
interestColumn.setMinWidth(200);
interestColumn.setCellValueFactory(new PropertyValueFactory<Finance, Double>("interest"));
TableColumn prinColumn = new TableColumn("Principal");
prinColumn.setMinWidth(200);
prinColumn.setCellValueFactory(new PropertyValueFactory<Finance, Double>("principal"));
TableColumn balColumn = new TableColumn("Balance");
balColumn.setMinWidth(200);
balColumn.setCellValueFactory(new PropertyValueFactory<Finance, Double>("balance"));
table.getColumns().addAll(paymentColumn, interestColumn, prinColumn, balColumn);
grid.add(labelTitle, 0, 0);
grid.add(new Label(), 0, 1);
grid.add(loan, 0, 2);
grid.add(loanAmount, 1, 2);
grid.add(years, 0, 3);
grid.add(numYears, 1, 3);
grid.add(interestRate, 0, 4);
grid.add(rate, 1, 4);
grid.add(compute, 4, 0);
scroll.setContent(table);
border.setTop(grid);
border.setBottom(scroll);
Scene scene = new Scene(border, 800, 800);
scene.getStylesheets().add("loan_table.css");
primaryStage.setScene(scene);
primaryStage.show();
compute.setOnAction((e) -> {
Double loanTotal = Double.parseDouble(loanAmount.getText());
Integer year = Integer.parseInt(numYears.getText());
Double rateInterest = Double.parseDouble(rate.getText());
double monthlyInterest = rateInterest / 1200;
double monthlyPayment = loanTotal * monthlyInterest / (1 - 1 / Math.pow(1 + monthlyInterest, year * 12));
balance = loanTotal;
for(int i = 1; i < year * 12; i++) {
interest = monthlyInterest * balance;
principal = monthlyPayment - interest;
balance = balance - principal;
payment = i + 1;
}
ObservableList<Finance> data = FXCollections.observableArrayList(new Finance(payment, interest, principal, balance));
table.setItems(data);
});
}
public static class Finance {
private final SimpleIntegerProperty payment;
private final SimpleDoubleProperty interest;
private final SimpleDoubleProperty principal;
private final SimpleDoubleProperty balance;
private Finance(int payment, double interest, double principal, double balance) {
this.payment = new SimpleIntegerProperty(payment);
this.interest = new SimpleDoubleProperty(interest);
this.principal = new SimpleDoubleProperty(principal);
this.balance = new SimpleDoubleProperty(balance);
}
public Integer getPayment(int numYears) {
return payment.get();
}
public Double getInterest(double rate) {
return interest.get();
}
public Double getPrincipal() {
return principal.get();
}
public Double getBalance() {
return balance.get();
}
}
public static void main(String[] args) {
launch(args);
}
}

Resources