I made a normal half pyramid in JavaFX with this code
public void buttonpressed() {
int iNum = Integer.parseInt(Num.getText());
String sResult = "";
String sNumber = "";
for (int i = 1; i<=iNum; i++ ) {
sNumber += i;
sResult += sNumber + "\n";
result.setText(sResult);
}
}
Which outputs (if the inputted number was 5)
1
12
123
1234
12345
Now I have to make 2 triangles (one normal and one inverted) which would look like this (inputted number = 5)
1 12345
12 1234
123 123
1234 12
12345 1
The problem is that I don't understand how to make an inverted triangle, I found how to make one in Java
public class Main {
public static void main(String[] args) {
int rows = 5;
for (int i = rows; i >= 1; --i) {
for (int j = 1; j <= i; ++j) {
System.out.print(j + " ");
}
System.out.println();
}
}
}
But I can't adapt it to JavaFX so please help me with that.
I tried copying the for loops and adapting it to JavaFX but it didn't work.
public void buttonpressed() {
int iNum = Integer.parseInt(Num.getText());
String sResult = "";
String sNumber = "";
for (int i = inum3; i >= 1; i--) {
for (int j = 1; j <= i; ++j) {
sNumber+=j;
sResult += sNumber +"\n";
}
result.setText(sResult);
}
}
The result I want is
12345
1234
123
12
1
Instead, with the provided code, I get
1
12
123
1234
12345
123451
1234512
12345123
123451234
123451234123
1234512341231
12345123412312
123451234123121
The whole code is
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class invertedtriangle extends Application{
TextField num = new TextField();
Label result = new Label();
public void start(Stage stage) {
GridPane p = new GridPane();
Scene scene = new Scene(p, 1000, 900);
Label input = new Label("Input a number");
Button press = new Button("Press");
p.add(input,0,1);
p.add(num, 0,2);
p.add(press, 0, 3);
p.add(result, 0, 4);
press.setOnAction(e -> buttonpressed());
p.setHgap(10.0);
p.setVgap(7.0);
stage.setTitle("inv triangle");
stage.setScene(scene);
stage.show();
}
public void buttonpressed() {
int iNum = Integer.parseInt(num.getText());
String sResult = "";
String sNumber = "";
for (int i = iNum; i >= 1; i--) {
for (int j = 1; j <= i; ++j) {
sNumber+=j;
sResult += sNumber +"\n";
}
result.setText(sResult);
}
}
public static void main(String[] args){
launch (args);
}
}
Just use the solution you already have, appending text to a string instead of sending it to the console:
public void buttonPressed() {
int iNum = Integer.parseInt(num.getText());
String sResult = "";
for (int i = iNum; i >= 1; --i) {
for (int j = 1; j <= i; ++j) {
// System.out.print(j + " ");
sResult = sResult + (j+" ");
}
// System.out.println();
sResult = sResult + "\n";
}
result.setText(sResult);
}
Note that it is far more efficient to use a StringBuilder instead of building up strings by concatenation (basically, in Java, you should never repeatedly perform string concatenation on the same string in a loop):
public void buttonPressed() {
int iNum = Integer.parseInt(num.getText());
StringBuilder text = new StringBuilder();
for (int i = iNum; i >= 1; --i) {
for (int j = 1; j <= i; ++j) {
text.append(j).append(" ");
}
text.append("\n");
}
result.setText(text.toString());
}
I have a barchat with 5 bars, now I'm trying to move each bar from left to right based on high value (more like a bar chart race from flourish # https://app.flourish.studio) not the same but the idea is the same.
To check small numbers against big numbers and big numbers against small numbers I'm using random integers.
For example, If barE is greater then all bars and less then barA, it should move to number 2 and replace bar B. I'm using the "if statement" to try and accomplish this. The problem is, only one transition is happening which is the first one. When random numbers change every 3 seconds the correct transition does not happen. Does anyone know how can I correct this problem?
import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Bounds;
import javafx.scene.Group;
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.chart.XYChart.Data;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class App extends Application {
private ScheduledExecutorService scheduledExecutorService;
final static String austria = "Austria", brazil = "Brazil", france = "France", england = "England", belgium = "Belgium";
private IntegerProperty secondA, secondB , secondC, secondD, secondE;
private Text secondAText, secondBText , secondCText, secondDText, secondEText;
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Realtime Bar Chart Demo");
//defining the axes
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setAnimated(false);
yAxis.setAnimated(false);
//creating the bar chart with two axis
final BarChart<String,Number> bc = new BarChart<>(xAxis,yAxis);
bc.setAnimated(false);
bc.setTitle("Country Summary");
xAxis.setLabel("Country");
yAxis.setLabel("Value");
//defining a series to display data
XYChart.Series<String, Number> seriesA = new XYChart.Series<>();
Data<String, Number> dataA = new XYChart.Data<>(austria,0);
seriesA.getData().add(dataA);
seriesA.setName("Austra");
secondA = new SimpleIntegerProperty(0);
secondAText = new Text("");
secondA.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
dataA.setYValue(newValue);
secondAText.setText(String.valueOf(newValue));
});
XYChart.Series<String, Number> seriesB = new XYChart.Series<>();
Data<String, Number> dataB = new XYChart.Data<>(brazil,0);
seriesB.getData().add(dataB);
seriesB.setName("Brazil");
secondB = new SimpleIntegerProperty(0);
secondB.bind(secondA.add(0));
secondBText = new Text("");
secondB.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
dataB.setYValue(newValue);
secondBText.setText(String.valueOf(newValue));
});
XYChart.Series<String, Number> seriesC = new XYChart.Series<>();
Data<String, Number> dataC = new XYChart.Data<>(france,0);
seriesC.getData().add(dataC);
seriesC.setName("France");
secondC = new SimpleIntegerProperty(0);
secondC.bind(secondA.add(0));
secondCText = new Text("");
secondC.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
dataC.setYValue(newValue);
secondCText.setText(String.valueOf(newValue));
});
XYChart.Series<String, Number> seriesD = new XYChart.Series<>();
Data<String, Number> dataD = new XYChart.Data<>(england,0);
seriesD.getData().add(dataD);
seriesD.setName("England");
secondD = new SimpleIntegerProperty(0);
secondD.bind(secondA.add(0));
secondDText = new Text("");
secondD.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
dataD.setYValue(newValue);
secondDText.setText(String.valueOf(newValue));
});
XYChart.Series<String, Number> seriesE = new XYChart.Series<>();
Data<String, Number> dataE = new XYChart.Data<>(belgium,0);
seriesE.getData().add(dataE);
seriesE.setName("Belgium");
secondE = new SimpleIntegerProperty(0);
secondE.bind(secondA.add(0));
secondEText = new Text("");
secondE.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
dataE.setYValue(newValue);
secondEText.setText(String.valueOf(newValue));
});
// add series to chart
bc.getData().add(seriesA);
bc.getData().add(seriesB);
bc.getData().add(seriesC);
bc.getData().add(seriesD);
bc.getData().add(seriesE);
displayLabelForData(dataA, secondAText);
displayLabelForData(dataB, secondBText);
displayLabelForData(dataC, secondCText);
displayLabelForData(dataD, secondDText);
displayLabelForData(dataE, secondEText);
// setup scene
Scene scene = new Scene(bc, 800, 600);
primaryStage.setScene(scene);
// show the stage
primaryStage.show();
// setup a scheduled executor to periodically put data into the chart
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
// input data onto graph per second scheduledExecutorService.scheduleAtFixedRate(() -> {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
double posA = dataA.getNode().localToScene(dataA.getNode().getBoundsInLocal()).getMinX();
double posB = dataB.getNode().localToScene(dataB.getNode().getBoundsInLocal()).getMinX();
double posC = dataC.getNode().localToScene(dataC.getNode().getBoundsInLocal()).getMinX();
double posD = dataD.getNode().localToScene(dataD.getNode().getBoundsInLocal()).getMinX();
double posE = dataE.getNode().localToScene(dataE.getNode().getBoundsInLocal()).getMinX();
TranslateTransition ttA = new TranslateTransition(Duration.millis(2000), dataA.getNode());
TranslateTransition ttB = new TranslateTransition(Duration.millis(2000), dataB.getNode());
TranslateTransition ttC = new TranslateTransition(Duration.millis(2000), dataC.getNode());
TranslateTransition ttD = new TranslateTransition(Duration.millis(2000), dataD.getNode());
TranslateTransition ttE = new TranslateTransition(Duration.millis(2000), dataE.getNode());
//Genarate random numbers
Integer randomB = ThreadLocalRandom.current().nextInt(60);
Integer randomC = ThreadLocalRandom.current().nextInt(60);
Integer randomD = ThreadLocalRandom.current().nextInt(60);
Integer randomE = ThreadLocalRandom.current().nextInt(60);
int intSecondB = secondB.bind(secondA.add(randomB));
int intSecondC = secondC.bind(secondA.add(randomC));
int intSecondD = secondD.bind(secondA.add(randomD));
int intSecondE = secondE.bind(secondA.add(randomE));
I'm not sure if the problem is with the below if statements or the code itself. To avoid the code for being more long, I only included if statements that compare only seriesB(barB) against other bars.
//using if statement to swich each bar based on value
if (intSecondB >= intSecondA && intSecondB >= intSecondB && intSecondB >= intSecondC && intSecondB >= intSecondD && intSecondB >= intSecondE) {
double diffBA = posB - posA;
ttA.setByX(diffBA);
ttB.setByX(-diffBA);
ttA.setCycleCount(1);
ttA.setAutoReverse(true);
ttA.play();
ttB.setCycleCount(1);
ttB.setAutoReverse(true);
ttB.play();
}
if (intSecondB < intSecondA && intSecondB >= intSecondC && intSecondB >= intSecondD && intSecondB >= intSecondE) {
System.out.println("keep seriesB(barB) at its position");
}
if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB >= intSecondD && intSecondB >= intSecondE) {
double diffCB = posC - posB;
ttB.setByX(diffCB);
ttC.setByX(-diffCB);
ttB.setCycleCount(1);
ttB.setAutoReverse(true);
ttB.play();
ttC.setCycleCount(1);
ttC.setAutoReverse(true);
ttC.play();
}
if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB >= intSecondE) {
double diffDB = posD - posB;
ttB.setByX(diffDB);
ttD.setByX(-diffDB);
ttB.setCycleCount(1);
ttB.setAutoReverse(true);
ttB.play();
ttD.setCycleCount(1);
ttD.setAutoReverse(true);
ttD.play();
}
if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {
double diffEB = posE - posB;
ttB.setByX(diffEB);
ttE.setByX(-diffEB);
ttB.setCycleCount(1);
ttB.setAutoReverse(true);
ttB.play();
ttE.setCycleCount(1);
ttE.setAutoReverse(true);
ttE.play();
}
if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {
double diffFB = posF - posB;
ttB.setByX(diffFB);
ttF.setByX(-diffFB);
ttB.setCycleCount(1);
ttB.setAutoReverse(true);
ttB.play();
ttF.setCycleCount(1);
ttF.setAutoReverse(true);
ttF.play();
}
if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {
double diffGB = posG - posB;
ttB.setByX(diffGB);
ttG.setByX(-diffGB);
ttB.setCycleCount(1);
ttB.setAutoReverse(true);
ttB.play();
ttG.setCycleCount(1);
ttG.setAutoReverse(true);
ttG.play();
}
if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {
double diffHB = posH - posB;
ttB.setByX(diffHB);
ttH.setByX(-diffHB);
ttB.setCycleCount(1);
ttB.setAutoReverse(true);
ttB.play();
ttH.setCycleCount(1);
ttH.setAutoReverse(true);
ttH.play();
}
if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {
double diffIB = posI - posB;
ttB.setByX(diffIB);
ttI.setByX(-diffIB);
ttB.setCycleCount(1);
ttB.setAutoReverse(true);
ttB.play();
ttI.setCycleCount(1);
ttI.setAutoReverse(true);
ttI.play();
}
if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {
double diffJB = posJ - posB;
ttB.setByX(diffJB);
ttJ.setByX(-diffJB);
ttB.setCycleCount(1);
ttB.setAutoReverse(true);
ttB.play();
ttJ.setCycleCount(1);
ttJ.setAutoReverse(true);
ttJ.play();
}
// Update the chart
Platform.runLater(() -> {
secondA.set( cal.get(Calendar.SECOND));
});
}, 0, 3, TimeUnit.SECONDS);
}
#Override
public void stop() throws Exception {
super.stop();
scheduledExecutorService.shutdownNow();
}
private void displayLabelForData(XYChart.Data<String, Number> data, Text text) {
final Node node = data.getNode();
((Group) node.getParent()).getChildren().add(text);
node.boundsInParentProperty().addListener((ChangeListener<Bounds>) (ov, oldBounds, bounds) -> {
text.setLayoutX(
Math.round( bounds.getMinX() + bounds.getWidth() / 2 - text.prefWidth(-1) / 2));
text.setLayoutY(Math.round( bounds.getMinY() - text.prefHeight(-1) * 0.5));
});
}
public static void main(String[] args) {
launch(args);
}
}
Any help will be appreciated!
Here is one strategy:
Create an ObservableList for the data
Create a SortedList from the underlying list
Register a listener with the SortedList, and when the data change, create an animation:
a. For each bar, find its current position and the position of the bar in the index corresponding to its new order
b. Use those positions to animate the translateX property of the bar
c. Animate the yValue property of the XYChart.Data in the same animation
d. At the end of the animation, reset the chart data to the new sorted data
There are a couple of little "gotchas" here: you need to turn off autoRanging on the CategoryAxis (else it will ignore changes to the order of the bars) and reset the categories using the new ordering when you update the data.
Here is an example. I created a class just to hold the data, without any chart API:
public static class CountryValue {
private final String country ;
private final double value ;
public CountryValue(String country, double value) {
super();
this.country = country;
this.value = value;
}
public String getCountry() {
return country;
}
public double getValue() {
return value;
}
}
and a simple data model to hold a list of these:
public static class Model {
private final ObservableList<CountryValue> values ;
public Model(CountryValue... countryValues) {
values = FXCollections.observableArrayList(countryValues) ;
}
public ObservableList<CountryValue> getValues() {
return values ;
}
}
Then the key parts look like:
Model model = new Model() ;
SortedList<CountryValue> sortedData = new SortedList<>(
model.getValues(),
Comparator.comparingDouble(CountryValue::getValue).reversed());
ObservableList<XYChart.Data<String, Number>> chartData = FXCollections.observableArrayList();
CategoryAxis countryAxis = new CategoryAxis();
countryAxis.setAutoRanging(false);
populateChartData(sortedData, chartData, countryAxis);
sortedData.addListener((Change<? extends CountryValue> c) -> {
Timeline timeline = new Timeline() ;
for (int newIndex = 0 ; newIndex < sortedData.size() ; newIndex++) {
CountryValue cv = sortedData.get(newIndex);
int currentIndex = indexByCountry(cv.getCountry(), chartData);
Data<String, Number> data = chartData.get(currentIndex);
double currentX = data.getNode().getBoundsInParent().getCenterX();
double targetX = chartData.get(newIndex).getNode().getBoundsInParent().getCenterX();
DoubleProperty translateXProperty = data.getNode().translateXProperty();
KeyValue kvx1 = new KeyValue(translateXProperty, 0);
KeyValue kvx2 = new KeyValue(translateXProperty, targetX - currentX);
ObjectProperty<Number> yValueProperty = data.YValueProperty();
KeyValue kvy1 = new KeyValue(yValueProperty, data.getYValue());
KeyValue kvy2 = new KeyValue(yValueProperty, cv.getValue());
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO, kvx1),
new KeyFrame(Duration.ZERO, kvy1),
new KeyFrame(animationDuration, kvx2),
new KeyFrame(animationDuration, kvy2)
);
}
timeline.setOnFinished(e -> populateChartData(sortedData, chartData, countryAxis));
timeline.play();
});
The utility populateChartData() method updates both the category axis and the data:
private void populateChartData(ObservableList<CountryValue> source,
ObservableList<XYChart.Data<String, Number>> chartData,
CategoryAxis countryAxis) {
countryAxis.getCategories().setAll(
source.stream()
.map(CountryValue::getCountry)
.collect(Collectors.toList())
);
chartData.setAll(
source.stream()
.map(cv -> new XYChart.Data<String, Number>(cv.getCountry(), cv.getValue()))
.collect(Collectors.toList())
);
}
Here's a complete example. The animation is a little "jerky"; I think because the y-axis scale changes in an unpredictable way. You could manage this yourself by turning off autoranging on the y-axis, computing the max y value from the new data, and animating the y-axis range as well as the bars. Also note that it's important no updates to the data happen while the animation is running (else you'll end up with multiple animations running at once). Here that's simply managed by timing, but a more robust solution would check for that and either throttle updates or just end the current animation before starting a new one.
import java.util.Comparator;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
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.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* JavaFX App
*/
public class FlourishChart extends Application {
private final Duration animationDuration = Duration.millis(250);
#Override
public void start(Stage stage) {
Model model = new Model() ;
Simulator simulator = new Simulator(model);
SortedList<CountryValue> sortedData = new SortedList<>(
model.getValues(),
Comparator.comparingDouble(CountryValue::getValue).reversed());
ObservableList<XYChart.Data<String, Number>> chartData = FXCollections.observableArrayList();
CategoryAxis countryAxis = new CategoryAxis();
countryAxis.setAutoRanging(false);
populateChartData(sortedData, chartData, countryAxis);
BarChart<String, Number> chart = new BarChart<>(countryAxis, new NumberAxis());
// turn off default animation:
chart.setAnimated(false);
Series<String, Number> series = new Series<>(chartData);
chart.getData().add(series);
// when sorted data change, animate bar chart nodes
// at end of animation, update chart data with new data
sortedData.addListener((Change<? extends CountryValue> c) -> {
Timeline timeline = new Timeline() ;
for (int newIndex = 0 ; newIndex < sortedData.size() ; newIndex++) {
CountryValue cv = sortedData.get(newIndex);
int currentIndex = indexByCountry(cv.getCountry(), chartData);
Data<String, Number> data = chartData.get(currentIndex);
double currentX = data.getNode().getBoundsInParent().getCenterX();
double targetX = chartData.get(newIndex).getNode().getBoundsInParent().getCenterX();
DoubleProperty translateXProperty = data.getNode().translateXProperty();
KeyValue kvx1 = new KeyValue(translateXProperty, 0);
KeyValue kvx2 = new KeyValue(translateXProperty, targetX - currentX);
ObjectProperty<Number> yValueProperty = data.YValueProperty();
KeyValue kvy1 = new KeyValue(yValueProperty, data.getYValue());
KeyValue kvy2 = new KeyValue(yValueProperty, cv.getValue());
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO, kvx1),
new KeyFrame(Duration.ZERO, kvy1),
new KeyFrame(animationDuration, kvx2),
new KeyFrame(animationDuration, kvy2)
);
}
timeline.setOnFinished(e -> populateChartData(sortedData, chartData, countryAxis));
timeline.play();
});
BorderPane root = new BorderPane(chart);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
new Thread(simulator).start();
}
private int indexByCountry(String country, ObservableList<Data<String, Number>> chartData) {
for (int index = 0 ; index < chartData.size(); index++) {
if (chartData.get(index).getXValue().equals(country))
return index ;
}
return -1 ;
}
private void populateChartData(ObservableList<CountryValue> source,
ObservableList<XYChart.Data<String, Number>> chartData,
CategoryAxis countryAxis) {
countryAxis.getCategories().setAll(
source.stream()
.map(CountryValue::getCountry)
.collect(Collectors.toList())
);
chartData.setAll(
source.stream()
.map(cv -> new XYChart.Data<String, Number>(cv.getCountry(), cv.getValue()))
.collect(Collectors.toList())
);
}
public static class Model {
private final ObservableList<CountryValue> values ;
public Model(CountryValue... countryValues) {
values = FXCollections.observableArrayList(countryValues) ;
}
public ObservableList<CountryValue> getValues() {
return values ;
}
}
// replace with record when they are standard in Java:
public static class CountryValue {
private final String country ;
private final double value ;
public CountryValue(String country, double value) {
super();
this.country = country;
this.value = value;
}
public String getCountry() {
return country;
}
public double getValue() {
return value;
}
}
// Not really relevant to problem; just simulates changing data in model
public class Simulator implements Runnable {
private final Model model ;
private final Random rng = new Random();
public Simulator(Model model) {
this.model = model ;
createData();
}
#Override
public void run() {
for (int i = 0 ; i < 10 ; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Platform.runLater(this::createData);
}
}
private void createData() {
model.getValues().setAll(
Stream.of("Austria", "Brazil", "France", "England", "Belgium")
.map(country -> new CountryValue(country, 50 * rng.nextDouble() + 50))
.collect(Collectors.toList())
);
}
}
public static void main(String[] args) {
launch();
}
}
I have a Tableview<ObservableList<Item>>, which is not updating when the underlying data is updated. Through debugging, I know that the underlying ObservableList<Item>> is being properly updated. I have ensured that all of Item's properties are visible, and in the format myFieldProperty().
Here is my table creation:
pattern= new TableView<>(mainApp.getItemList());
for (ObservableList<Item> row : pattern.getItems()) {
for (int i= pattern.getColumns().size(); i<row.size(); i++){
final int columnIndex = i ;
TableColumn<ObservableList<Item>, Color> column = new TableColumn<>();
column.setCellValueFactory( rowData ->
rowData.getValue()
.get(columnIndex).displayColorProperty()); // the Item for this cell
column.setCellFactory(col -> {
ItemCell cell = new ItemCell();
cell.setOnMouseEntered( e -> {
if (cell.getItem() != null) {
#SuppressWarnings("unchecked")
ObservableList<Item> stitchRow =
(ObservableList<Item>) cell.getTableRow().getItem();
mainApp.getRLController().setItemLabel(itemRow.get(columnIndex).toString());
}
});
cell.setOnMouseExited( e -> {
mainApp.getRLController().setItemLabel(null);
});
cell.setOnMouseClicked((MouseEvent e) -> {
Item newItem = mainApp.getTBController().getSelectedItem();
if (e.getButton() == MouseButton.PRIMARY && newItem != null) {
ObservableList<Item> itemRow =
(ObservableList<Item>) cell.getTableRow().getItem();
itemRow.set(columnIndex, newItem);
mainApp.getRLController().setItemLabel(itemRow.get(columnIndex).toString());
}
});
return cell;
});
column.setMinWidth(7);
column.setPrefWidth(7);
column.setMaxWidth(7);
pattern.getColumns().add(column);
}
}
pattern.setFixedCellSize(7);
pattern.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);`
Code for my Custom Cell Factory:
public class ItemCell extends TableCell<ObservableList<Item>, Color> {
#Override
protected void updateItem(Color color, boolean empty) {
super.updateItem(color, empty);
if (empty || color == null) {
setText(null);
setStyle(null);
} else {
int r = (int) (color.getRed() * 255);
int g = (int) (color.getGreen() * 255);
int b = (int) (color.getBlue() * 255);
this.setStyle("-fx-background-color: rgb(" + r + "," + g + "," + b + ");"
+ "-fx-border-color: black; -fx-table-cell-border-color: black;");
}
}
}
The basic problem is that the object you are changing (the item which is an element of the list representing the row) is not the property that the cell is observing for changes (the displayColorProperty() belonging to the item). You need to arrange to change the value of a property that the cell is observing.
Three possible solutions:
If possible, just change the displayColor (and other data too) of the item displayed by the cell. I.e.
cell.setOnMouseClicked((MouseEvent e) -> {
if (e.getButton() == MouseButton.PRIMARY && newItem != null) {
ObservableList<Item> itemRow =
(ObservableList<Item>) cell.getTableRow().getItem();
Item item = itemRow.get(columnIndex);
item.setDisplayColor(...);
item.set...(...);
// ...
mainApp.getRLController().setItemLabel(item.toString());
}
});
Or, replace the entire row:
cell.setOnMouseClicked((MouseEvent e) -> {
Item newItem = mainApp.getTBController().getSelectedItem();
if (e.getButton() == MouseButton.PRIMARY && newItem != null) {
ObservableList<Item> itemRow =
(ObservableList<Item>) cell.getTableRow().getItem();
ObservableList<Item> newRow = FXCollections.observableArrayList(itemRow);
newRow.set(columnIndex, newItem);
pattern.getItems().set(cell.getTableRow().getIndex(), newRow);
mainApp.getRLController().setItemLabel(newRow.get(columnIndex).toString());
}
});
Otherwise, you could make your table a TableView<ObservableList<ObjectProperty<Item>>>. This gets a little tricky but it's not too bad. This way you can just set the value of the object property to your new item.
Here's a complete example using the third technique:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ColorTableExample extends Application {
#Override
public void start(Stage primaryStage) {
TableView<ObservableList<ObjectProperty<Item>>> table = new TableView<>();
int NUM_ROWS = 20 ;
int NUM_COLS = 15 ;
ObservableList<ObservableList<ObjectProperty<Item>>> data = table.getItems() ;
for (int y = 0 ; y < NUM_ROWS; y++) {
ObservableList<ObjectProperty<Item>> row = FXCollections.observableArrayList();
data.add(row);
double saturation = (1.0 * y) / NUM_ROWS ;
for (int x = 0 ; x < NUM_COLS; x++) {
double hue = x * 360.0 / NUM_COLS ;
Color color = Color.hsb(hue, saturation, 1.0);
row.add(new SimpleObjectProperty<>(new Item(color)));
}
}
for (ObservableList<ObjectProperty<Item>> row : table.getItems()) {
for (int i = table.getColumns().size() ; i < row.size(); i++) {
int columnIndex = i ;
TableColumn<ObservableList<ObjectProperty<Item>>, Item> column = new TableColumn<>(Integer.toString(i+1));
column.setCellValueFactory(rowData -> rowData.getValue().get(columnIndex));
column.setCellFactory(c -> {
TableCell<ObservableList<ObjectProperty<Item>>, Item> cell = new TableCell<ObservableList<ObjectProperty<Item>>, Item>() {
#Override
public void updateItem(Item item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setStyle("");
} else {
Color color = item.getDisplayColor() ;
int r = (int) (color.getRed() * 255) ;
int g = (int) (color.getGreen() * 255) ;
int b = (int) (color.getBlue() * 255) ;
String style = String.format(
"-fx-background-color: rgb(%d, %d, %d);"
+ "-fx-border-color: black ;"
+ "-fx-table-cell-border-color: black ;"
,r, g, b);
setStyle(style);
}
}
};
cell.setOnMousePressed(evt -> {
if (! cell.isEmpty()) {
ObservableList<ObjectProperty<Item>> rowData = (ObservableList<ObjectProperty<Item>>) cell.getTableRow().getItem();
Color currentColor = cell.getItem().getDisplayColor();
double newHue = ( currentColor.getHue() + 15 ) % 360 ;
Color newColor = Color.hsb(newHue, currentColor.getSaturation(), currentColor.getBrightness());
rowData.get(columnIndex).set(new Item(newColor));
}
});
return cell ;
});
table.getColumns().add(column);
}
}
BorderPane root = new BorderPane(table, null, null, null, null);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
public static class Item {
private final ObjectProperty<Color> displayColor = new SimpleObjectProperty<>() ;
public Item(Color color) {
this.displayColorProperty().set(color);
}
public final ObjectProperty<Color> displayColorProperty() {
return this.displayColor;
}
public final javafx.scene.paint.Color getDisplayColor() {
return this.displayColorProperty().get();
}
public final void setDisplayColor(final javafx.scene.paint.Color displayColor) {
this.displayColorProperty().set(displayColor);
}
}
public static void main(String[] args) {
launch(args);
}
}
(At some point, it might be easier to refactor everything so that you have an actual class representing each row in the table, instead of using a list.)
There may also be a clever workaround using an extractor for the list, but I couldn't make that work.
I am trying to color different region of a polar chart with different colors. e.g coloring the region between the angle 20 and 60 and between the radii 2 and 4.
How can I do this? I was thinking of using a shape annotation and from there drawing an arc, but it seems there is no shape annotation for polar plots.
Any ideas?
Thank you
import java.awt.Color;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTick;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PolarPlot;
import org.jfree.chart.renderer.DefaultPolarItemRenderer;
import org.jfree.chart.renderer.PolarItemRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.TextAnchor;
public class test2 extends JFrame {
private static final String title = "Archimedes' Spiral";
public test2(String title) {
super(title);
JFreeChart chart = createChart(createDataset());
ChartPanel panel = new ChartPanel(chart);
panel.setPreferredSize(new Dimension(500, 500));
panel.setMouseZoomable(false);
this.add(panel);
}
private static XYDataset createDataset() {
XYSeriesCollection result = new XYSeriesCollection();
XYSeries series = new XYSeries(title);
XYSeries ser = new XYSeries("test");
for (int t = 0; t <= 1 * 360; t++) {
series.add(90 - t, t);
}
for (int t = 0; t <= 1 * 120; t++) {
ser.add(90 - t, 40);
ser.add(90 - t, 120);
}
result.addSeries(series);
result.addSeries(ser);
return result;
}
private static JFreeChart createChart(XYDataset dataset) {
ValueAxis radiusAxis = new NumberAxis();
radiusAxis.setTickLabelsVisible(false);
PolarItemRenderer renderer = new DefaultPolarItemRenderer();
PolarPlot plot = new PolarPlot(dataset, radiusAxis, renderer) {
#Override
protected List refreshAngleTicks() {
List<NumberTick> ticks = new ArrayList<NumberTick>();
int delta = (int) this.getAngleTickUnit().getSize();
for (int t = 0; t < 360; t += delta) {
int tp = (360 + 90 - t) % 360;
NumberTick tick = new NumberTick(
Double.valueOf(t), String.valueOf(tp),
TextAnchor.CENTER, TextAnchor.CENTER, 0.0);
ticks.add(tick);
}
return ticks;
}
};
plot.setBackgroundPaint(new Color(0x00f0f0f0));
plot.setRadiusGridlinePaint(Color.gray);
plot.addCornerTextItem("r(θ) = θ; 0 < θ < 6π");
DefaultPolarItemRenderer ren = new DefaultPolarItemRenderer();
ren.setSeriesFilled(0, true);
ren.setSeriesFilled(1, true);
plot.setRenderer(ren);
JFreeChart chart = new JFreeChart(
title, JFreeChart.DEFAULT_TITLE_FONT, plot, true);
chart.setBackgroundPaint(Color.white);
return chart;
}
public static void main(String[] args) {
test2 demo = new test2(title);
demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
demo.pack();
demo.setLocationRelativeTo(null);
demo.setVisible(true);
}
}
The DefaultPolarItemRenderer typically used in a PolarPlot has the method setSeriesFilled(), which controls whether a series is filled. The renderer specifies the AlphaComposite.SRC_OVER mode with a value of 50%, so overlapping fills look especially nice.
Addendum: To create the chart seen below, start with this example and reduce the data set's domain from 6π to 2π in createDataset():
for (int t = 0; t <= 1 * 360; t++) { ...
Then make the series filled in createChart():
...
DefaultPolarItemRenderer renderer = new DefaultPolarItemRenderer();
renderer.setSeriesFilled(0, true);
...
I am trying to create a filled series over each region of a polar plot. But the colors are not filled out right. Here is an image of what I get: http://i1122.photobucket.com/albums/l539/jpo2/polar-1.gif
Here is the code I have to loop through a given section is as follows:
if (i < 8) {
for(int r = 0; r< 20; r+=(NumberAxis) plot.getAxis()).getTickUnit().getSize()){
for(int theta = 0; theta <= 180; theta+=30){
XYSeries series = new XYSeries(i + "π/8 " + "< θ < 2π+" + i + "π/8");
for (int e = theta; e < theta+30; e++) {
series.add(90-e-i*45, r);
series.add(90-e-i*45, r- ((NumberAxis) plot.getAxis()).getTickUnit().getSize());
}
result.addSeries(series);
setFilled(result);
i++;
}
}
}
private void setFilled(XYDataset dataset) {
for (int i = 0; i < dataset.getSeriesCount(); i++) {
renderers.setSeriesFilled(i, true);
}
}
This is a modification of #trashgod's draw method at JFreechart Loop through polar chart sectors
Please help.
Full code:
import java.awt.Color;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTick;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.event.ChartProgressEvent;
import org.jfree.chart.event.ChartProgressListener;
import org.jfree.chart.plot.PolarPlot;
import org.jfree.chart.renderer.DefaultPolarItemRenderer;
import org.jfree.chart.renderer.PolarItemRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.TextAnchor;
public class tests extends JFrame implements ChartProgressListener {
private static final String title = "Archimedes' Spirals";
private XYSeriesCollection result = new XYSeriesCollection();
private DefaultPolarItemRenderer renderers = new DefaultPolarItemRenderer();
private int i;
public tests(String title) {
super(title);
JFreeChart chart = createChart(result);
ChartPanel panel = new ChartPanel(chart);
panel.setPreferredSize(new Dimension(500, 500));
panel.setMouseZoomable(false);
this.add(panel);
}
private JFreeChart createChart(XYDataset dataset) {
ValueAxis radiusAxis = new NumberAxis();
radiusAxis.setTickLabelsVisible(false);
PolarItemRenderer renderer = new DefaultPolarItemRenderer();
PolarPlot plot = new PolarPlot(dataset, radiusAxis, renderer) {
#Override
protected List refreshAngleTicks() {
List<NumberTick> ticks = new ArrayList<NumberTick>();
int delta = (int) this.getAngleTickUnit().getSize();
for (int t = 0; t < 360; t += delta) {
int tp = (360 + 90 - t) % 360;
NumberTick tick = new NumberTick(
Double.valueOf(t), String.valueOf(tp),
TextAnchor.CENTER, TextAnchor.CENTER, 0.0);
ticks.add(tick);
}
return ticks;
}
};
plot.setBackgroundPaint(new Color(0x00f0f0f0));
plot.setRadiusGridlinePaint(Color.gray);
plot.addCornerTextItem("r(θ) = θ; 0 < θ < 2π; +iπ/8");
setFilled(dataset);
plot.setRenderer(renderers);
JFreeChart chart = new JFreeChart(
title, JFreeChart.DEFAULT_TITLE_FONT, plot, true);
chart.setBackgroundPaint(Color.white);
chart.addProgressListener(this);
return chart;
}
public static void main(String[] args) {
tests demo = new tests(title);
demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
demo.pack();
demo.setLocationRelativeTo(null);
demo.setVisible(true);
}
#Override
public void chartProgress(ChartProgressEvent e) {
if (e.getType() == ChartProgressEvent.DRAWING_FINISHED) {
System.out.println(e);
JFreeChart chart = e.getChart();
draw();
}
}
public void draw() {
if (i < 4) {
for (int g = 0; g < 30; g += 5) {
for (int h = 0; h < 180; h += 45) {
XYSeries series = new XYSeries(i + "π/8 " + "< θ < 2π+" + i + "π/8");
for (int t = h; t <= h + 45; t++) {
series.add(90 - t, g);
series.add(90 - t, g + 5);
}
result.addSeries(series);
setFilled(result);
i++;
}
}
}
}
private void setFilled(XYDataset dataset) {
for (int i = 0; i < dataset.getSeriesCount(); i++) {
renderers.setSeriesFilled(i, true);
}
}
}
Here's a simplified version the uses PolarPlot directly, without any transformation. It might be easier to experiment with.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PolarPlot;
import org.jfree.chart.renderer.DefaultPolarItemRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
/** #see http://stackoverflow.com/questions/6669734 */
public class PolarArcs {
private static final String title = "PolarArcs";
private static final double PI2 = 90d; // π/2 radians = 90°
private void display() {
JFrame f = new JFrame(title);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ChartPanel panel = new ChartPanel(createChart(createDataset()));
panel.setPreferredSize(new Dimension(400, 400));
f.add(panel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private JFreeChart createChart(XYDataset dataset) {
JFreeChart chart = ChartFactory.createPolarChart(
title, dataset, true, false, false);
PolarPlot plot = (PolarPlot) chart.getPlot();
plot.setBackgroundPaint(Color.white);
plot.setAngleGridlinesVisible(false);
plot.setRadiusGridlinesVisible(false);
DefaultPolarItemRenderer r = (DefaultPolarItemRenderer) plot.getRenderer();
for (int i = 0; i < dataset.getSeriesCount(); i++ ) {
r.setSeriesFilled(i, true);
}
NumberAxis rangeAxis = (NumberAxis) plot.getAxis();
rangeAxis.setTickLabelsVisible(false);
return chart;
}
private XYDataset createDataset() {
XYSeriesCollection result = new XYSeriesCollection();
for (int r = 8; r > 0; r--) {
XYSeries series = new XYSeries(title + String.valueOf(r));
for (int t = (int) -PI2; t <= PI2; t++) {
series.add(t, r);
}
result.addSeries(series);
}
return result;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new PolarArcs().display();
}
});
}
}