How to auto-scale JavaFX LineChart if an invisible series present? - javafx

I need a possibility of show/hide the series on my LineChart. For now I'm doing that like this:
series.getNode().setVisible(false);
But though the series is invisible after this, a chart axis autorange still takes into account an range of the series data. I tired set up managed property also:
series.getNode().setVisible(false);
series.getNode().setManaged(false);
but without success. So, is there a some way to hide a series and exclude it from the axis autorange?

Seems, the only way to do so for now is to extend LineChart and override updateAxisRange() to modify it to consider only visible series:
public class MLineChart<X, Y> extends LineChart<X, Y> {
public MLineChart(#NamedArg("xAxis") Axis<X> xAxis, #NamedArg("yAxis") Axis<Y> yAxis) {
super(xAxis, yAxis);
}
#Override
protected void updateAxisRange() {
final Axis<X> xa = getXAxis();
final Axis<Y> ya = getYAxis();
List<X> xData = null;
List<Y> yData = null;
if (xa.isAutoRanging()) xData = new ArrayList<X>();
if (ya.isAutoRanging()) yData = new ArrayList<Y>();
if (xData != null || yData != null) {
for (Series<X, Y> series : getData()) {
if (series.getNode().isVisible()) { // consider only visible series
for (Data<X, Y> data : series.getData()) {
if (xData != null) xData.add(data.getXValue());
if (yData != null) yData.add(data.getYValue());
}
}
}
// RT-32838 No need to invalidate range if there is one data item - whose value is zero.
if (xData != null && !(xData.size() == 1 && getXAxis().toNumericValue(xData.get(0)) == 0)) {
xa.invalidateRange(xData);
}
if (yData != null && !(yData.size() == 1 && getYAxis().toNumericValue(yData.get(0)) == 0)) {
ya.invalidateRange(yData);
}
}
}
}

Related

Xamarin forms: Issue with multiple events click on same day in XamForms.Controls.Calendar

I am using XamForms.Controls.Calendar for showing events on the calendar. I have give color for special dates on the calendar using the following code:
private void AddSpecialDateWithList(List<events> list)
{
List<SpecialDate> newList = new List<SpecialDate>();
foreach (events model in list)
{
if (string.IsNullOrWhiteSpace(model.end))
{
string date = model.start;
int index = date.IndexOf('T');
if (index > 0)
{
date = date.Substring(0, index);
}
var newDate = AddDate(DateTime.Parse(date), model);
newList.Add(newDate);
newEventsList.Add(model);
}
else
{
string startDate = model.start;
int startIndex = startDate.IndexOf('T');
if (startIndex > 0)
{
startDate = startDate.Substring(0, startIndex);
}
string endDate = model.end;
int endIndex = endDate.IndexOf('T');
if (endIndex > 0)
{
endDate = endDate.Substring(0, endIndex);
}
List<DateTime> dates = GetDatesBetween(DateTime.Parse(startDate), DateTime.Parse(endDate));
for (int i = 0; i < dates.Count; i++)
{
var newDate = AddDate(dates[i], model);
newList.Add(newDate);
newEventsList.Add(model);
}
}
}
calendar.SpecialDates = newList;
}
private SpecialDate AddDate(DateTime dateTime, events model)
{
SpecialDate newDate = new SpecialDate(dateTime)
{
Selectable = true,
BackgroundColor = Color.FromHex("#fec208"),
TextColor = Color.White
};
return newDate;
}
public List<DateTime> GetDatesBetween(DateTime startDate, DateTime endDate)
{
List<DateTime> allDates = new List<DateTime>();
for (DateTime date = startDate; date <= endDate; date = date.AddDays(1))
allDates.Add(date);
return allDates;
}
I have also enabled clicked event for special dates:
private async void calendar_DateClicked(object sender, DateTimeEventArgs e)
{
int num = 0;
var specialList = calendar.SpecialDates;
var date = e.DateTime;
selectedEventsList.Clear();
foreach (SpecialDate specialDate in specialList)
{
if (specialDate.Date.Year == date.Year && specialDate.Date.Month == date.Month && specialDate.Date.Day == date.Day)
{
events model = new events();
model = newEventsList[num];
selectedEventsList.Add(model);
}
else
{
num++;
}
}
if (selectedEventsList.Count == 1)
{
await DisplayAlert("Alert", "successs.", "Ok");
}
else
{
eventTitleList.Clear();
for (int j = 0; j < selectedEventsList.Count; j++)
{
eventTitleList.Add(selectedEventsList[j].title);
}
string action = await DisplayActionSheet(null, "Cancel", null, eventTitleList.ToArray());
for (int k = 0; k < eventTitleList.Count; k++)
{
if (action == eventTitleList[k])
{
//next page
}
}
}
}
When multiple events coming on the same day I need to show the events as a pop-up window. Then the user is able to select the event from the pop-up and go to the details page. I have implemented this on the above code, but sometimes duplicates events are showing on the pop-up window.
I have created a sample project for reproducing the issue. In the sample project, on 7May, the events are Chemistry Assignment and English Project. But Chemistry Assignment is showing duplicate. 10 may events are Chemistry Assignment and Physics, but showing Chemistry Assignment and English Project. I checked this lot of times, but didn't find the reason behind this.
The error caused by that the newEventsList do not get the correct index.
Change the code in calendar_DateClicked event from:
foreach (SpecialDate specialDate in specialList)
{
if (specialDate.Date.Year == date.Year && specialDate.Date.Month == date.Month && specialDate.Date.Day == date.Day)
{
events model = new events();
model = newEventsList[num];
selectedEventsList.Add(model);
}
else
{
num++;
}
}
To:
foreach (SpecialDate specialDate in specialList)
{
if (specialDate.Date.Year == date.Year && specialDate.Date.Month == date.Month && specialDate.Date.Day == date.Day)
{
events model = new events();
model = newEventsList[num];
selectedEventsList.Add(model);
}
num++;
}
Screenshot:

In Javafx how to set the size cells of a gridpane a fraction of the parent pane?

I try to create a gridPane in JavaFx with a circle in it.I want the gridPane cells to use all the available space in the gridPane.(The GridPane is in the Center of a BorderPane) but the cells keep resizing to the dimensions of the inner objects.How do I get the cells to use all space available? (and how do I set the radius of the circle to a fraction of the space available in the Center of the BorderPane.
I am quite new to JavaFx but I tried to use Columnconstraints and RowConstraints to match my need. It didn't work.I tried also to bind the size of my objects in the GridPane to use a fraction of the stage size but it does not work properly as it does not correspond to the plane in the BorderPane.
public void start(Stage primaryStage) throws Exception{
BorderPane applicationLayout = new BorderPane();
primaryStage.setTitle("Multi-level feedback simulator");
Scene scene = new Scene(applicationLayout, 600, 600);
primaryStage.setScene(scene);
//Add the menu Bar
//MainMenuBar menuBar = new MainMenuBar(primaryStage);
//applicationLayout.setTop(menuBar);
//Add the main zone of drawing
TreeDrawingZone treeDrawingZone = new TreeDrawingZone(primaryStage,applicationLayout,3,3);
applicationLayout.setCenter(treeDrawingZone);
primaryStage.show();
primaryStage.setMaximized(true);
}
The GridPane code with the constraints.
The biggest part of the constructor creates lines dans circles to display a tree.
The drawings functions are createLine() and createCircle()
public class TreeDrawingZone extends Parent {
private GridPane drawingZoneLayout;
private Stage stage;
private int columnNumber;
private int rowNumber;
private Pane rootPane;
private List<Pair<Integer,Integer>> circlePositions;
public TreeDrawingZone(Stage stage,Pane rootPane, int treeHeight, int childrenPerNode){
this.stage = stage;
drawingZoneLayout = new GridPane();
columnNumber = 2*(int)Math.pow(childrenPerNode,treeHeight-1)-1;
rowNumber = 2*treeHeight-1;
circlePositions = new ArrayList<>();
this.rootPane = rootPane;
//TODO Use the correct height of the borderLayout (maybe with a upper level layout)
System.out.println(columnNumber);
System.out.println(rowNumber);
//column Constraints
for(int i = 1 ; i <= columnNumber ; i++){
ColumnConstraints columnConstraints = new ColumnConstraints();
columnConstraints.setPercentWidth((double) 100/columnNumber);
columnConstraints.setFillWidth(true);
drawingZoneLayout.getColumnConstraints().add(columnConstraints);
}
//row Constraints
for(int i = 1 ; i <= rowNumber ; i++){
RowConstraints rowConstraints = new RowConstraints();
rowConstraints.setPercentHeight((double) 100/rowNumber);
rowConstraints.setFillHeight(true);
drawingZoneLayout.getRowConstraints().add(rowConstraints);
}
//Tree Representation
//Base Line
List<Integer> circleLineRepartition = new ArrayList<>();
for(int i = 0 ; i < columnNumber; i ++){
if(i % 2 == 0){
circleLineRepartition.add(i);
}
}
System.out.println(circleLineRepartition);
//Creation of the grid line per line
for(int i = rowNumber-1 ; i >=0 ; i-=2){
if(i % 2 == 0) {
//Case of the line with circles
for (Integer circlePosition : circleLineRepartition) {
Pane circlePane;
if (i == 0) {
circlePane = createCircle(true, false);
} else if (i == rowNumber - 1) {
circlePane = createCircle(false, true);
} else {
circlePane = createCircle();
}
drawingZoneLayout.add(circlePane, circlePosition, i);
circlePositions.add(new Pair<>(circlePosition, i));
}
List<Integer> upperCircleLineRepartition;
//Create the lines
//The following block enumerates the different cases to create the lines between the dotes
try {
upperCircleLineRepartition = getoddlyRepartedCenters(childrenPerNode, circleLineRepartition);
if (i > 0) {
int minPosition = circleLineRepartition.get(0);
int maxPosition = circleLineRepartition.get(circleLineRepartition.size() - 1);
int position = 0;
boolean drawHorizontal = true;
int linkedNodeCount = 0;
for (int j = minPosition; j <= maxPosition; j++) {
Pane linesPane;
if (j == circleLineRepartition.get(position) && minPosition != maxPosition) {
//Update the number of linked Nodes
if(drawHorizontal) {
linkedNodeCount += 1;
if(linkedNodeCount == childrenPerNode)
drawHorizontal = false;
}else{
linkedNodeCount = 1;
drawHorizontal = true;
}
//First element
if (linkedNodeCount == 1) {
if(upperCircleLineRepartition.contains(j)){
linesPane = createLines(LineDirection.NORTH,LineDirection.SOUTH,LineDirection.EAST);
}else {
linesPane = createLines(LineDirection.SOUTH, LineDirection.EAST);
}
}
//Last element
else if (linkedNodeCount == childrenPerNode) {
if(upperCircleLineRepartition.contains(j)){
linesPane = createLines(LineDirection.NORTH,LineDirection.SOUTH,LineDirection.WEST);
}else {
linesPane = createLines(LineDirection.WEST, LineDirection.SOUTH);
}
}//bridge with under and upper level
else if(upperCircleLineRepartition.contains(j)) {
linesPane = createLines(LineDirection.SOUTH, LineDirection.NORTH, LineDirection.EAST, LineDirection.WEST);
}
//other children
else{
linesPane = createLines(LineDirection.SOUTH, LineDirection.EAST, LineDirection.WEST);
}
position++;
}
//Only one child
else if (minPosition == maxPosition) {
linesPane = createLines(LineDirection.SOUTH, LineDirection.NORTH);
}
//Bridge between children
else {
if(drawHorizontal) {
if (upperCircleLineRepartition.contains(j)) {
linesPane = createLines(LineDirection.NORTH, LineDirection.EAST, LineDirection.WEST);
} else {
linesPane = createLines(LineDirection.WEST, LineDirection.EAST);
}
}else{
linesPane = createLines();
}
}
drawingZoneLayout.add(linesPane, j, i - 1);
}
}
circleLineRepartition = new ArrayList<>(upperCircleLineRepartition);
} catch (Exception e) {
System.out.println("Invalid line given");
}
}
}
drawingZoneLayout.setMaxSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE);
//TODO remove GridLines after debug
drawingZoneLayout.setGridLinesVisible(true);
this.getChildren().add(drawingZoneLayout);
}
private Pane createCircle(){
return createCircle(false,false);
}
private Pane createCircle(boolean isRoot, boolean isLeaf){
Pane circlePane = new Pane();
Circle circle = new Circle();
circle.centerXProperty().bind(stage.widthProperty().divide(columnNumber).divide(2));
circle.centerYProperty().bind(stage.heightProperty().divide(rowNumber).divide(2));
circle.radiusProperty().bind(Bindings.min(stage.widthProperty().divide(columnNumber).divide(2),stage.heightProperty().divide(rowNumber).divide(2)));
circlePane.getChildren().add(circle);
if(!isLeaf) {
circlePane.getChildren().add(createLines(LineDirection.SOUTH));
}
if(!isRoot){
circlePane.getChildren().add(createLines(LineDirection.NORTH));
}
return circlePane;
}
private Pane createLines(LineDirection ... directions){
Pane linesGroup = new Pane();
for(LineDirection direction : directions){
linesGroup.getChildren().add(createLine(direction));
}
return linesGroup;
}
private Line createLine(LineDirection direction){
Line line = new Line();
if(direction == LineDirection.EAST || direction == LineDirection.WEST){
line.startYProperty().bind(stage.heightProperty().divide(rowNumber).divide(2));
line.endYProperty().bind(stage.heightProperty().divide(rowNumber).divide(2));
line.startXProperty().bind(stage.widthProperty().divide(columnNumber).divide(2));
if(direction == LineDirection.EAST){
line.endXProperty().bind(stage.widthProperty().divide(columnNumber));
}
else{
line.setEndX(0);
}
}
else{
line.startXProperty().bind(stage.widthProperty().divide(columnNumber).divide(2));
line.endXProperty().bind(stage.widthProperty().divide(columnNumber).divide(2));
line.startYProperty().bind(stage.heightProperty().divide(rowNumber).divide(2));
if(direction == LineDirection.NORTH){
line.setEndY(0);
}else{
line.endYProperty().bind(stage.heightProperty().divide(rowNumber));
}
}
line.setStrokeWidth(1);
line.setFill(null);
line.setStroke(Color.BLACK);
return line;
}
private int getCenter(List<Integer> childrenNodesPosition) throws Exception {
if (childrenNodesPosition.size() == 0){
throw new Exception("Tried to get the center of an empty list");
}else{
int sum = 0;
for(int childNodePosition : childrenNodesPosition){
sum += childNodePosition;
}
return sum/childrenNodesPosition.size();
}
}
private List<Integer> getoddlyRepartedCenters(int nodeNumberPerParent, List<Integer> childrenNodesPosition) throws Exception {
int parentNumber = childrenNodesPosition.size()/nodeNumberPerParent;
int nextPosition = 0;
List<Integer> regularParentCenters = new ArrayList<>(parentNumber);
for(int i = 0 ; i < parentNumber ; i++){
regularParentCenters.add(getCenter(childrenNodesPosition.subList(nextPosition,nextPosition + nodeNumberPerParent)));
nextPosition = nextPosition + nodeNumberPerParent;
}
return regularParentCenters;
}
}
The result that I want to correct

How to show desired number of setTickLabelsVisible false in line chart javafx

I am trying to build a time series line chart for stock market data. I am getting the input dynamically, and its been plotted on the line chart.
All I want is to plot every tick data on the graph but want to show only desired number of tick labels.
Ex: Say I started to build the graph on 10 AM, and i am plotting on 3 sec, so after 4 hours, i.e 1 PM, i want every tick to be plotted but the the label on Xaxis should be of my choice, say only 4 tick labels (10AM,11AM,12AM,1PM).
Kindly see the desired graph:
Thanks a lot in advance. :)
Code:
#FXML
private void buttonGraph(ActionEvent event) throws ParseException{
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("HH:mm:ss");
LocalTime initTime = LocalTime.now();
Cluster cluster = Cluster.builder().addContactPoint("127.0.0.1").build();
Session session = cluster.connect("pass_master");
lineChart.getData().clear();
ResultSet results2 = session.execute("select count(*) as ct from pass8 where tkn ="+basic.get(tkn.getText())+" and timestmp <= "+etDate+" and timestmp >= "+stDate);
String total = null;
for( Row row : results2){
total = row.toString();
}
System.out.println(total);
System.out.println(total.substring(4, total.length()-1));
int totalrows = Integer.valueOf(total.substring(4, total.length()-1));
System.out.println("This is int value : "+totalrows);
XYChart.Series<String,Double> series = new XYChart.Series<>();
y.setAutoRanging(false);
y.setSide(Side.RIGHT);
if (graphUpdater == null) {
graphUpdater =new Thread(new Runnable() {
double min = 10000000;
double max = 0;
String chartData ="";
String invisible = "";
int i = 0;
XYChart.Data item;
public void run() {
while(running) {
try {
Quote quote = MulticastReciever.getInstance().getQuote(tkn.getText());
if (quote!=null) {
LocalDateTime now = LocalDateTime.now();
double value = quote.getLast();
if (value < min) {
min = value;
}
if (value > max) {
max = value;
}
i++;
a1.add(value);
if (i % 5 == 0 ){
chartData = dtf.format(now);
x.setTickLabelFill(Color.RED);
item = new XYChart.Data<>(chartData, value);
}
else {
invisible += (char)32;
chartData = dtf.format(now);
x.setTickMarkVisible(false);
item = new XYChart.Data<>(chartData, value);
}
series.getData().add(item);
lineChart.setCreateSymbols(false);
lineChart.setLegendVisible(false);
y.setLowerBound(min-(.001*min));
y.setUpperBound(max+(.001*max));
y.setTickLabelFill(Color.RED);
x.setTickMarkVisible(false);
y.setTickMarkVisible(false);
}
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
graphUpdater.start();
}
System.out.println("previousToken:"+previousToken+", GC:"+this);
int currentToken = Integer.valueOf(basic.get(tkn.getText()));
if (previousToken!=null) {
System.out.println("Going to unsubscribe token:"+previousToken);
MulticastReciever.getInstance().unsubscribeMarketData(previousToken);
}
MulticastReciever.getInstance().subscribeMarketData(tkn.getText(), currentToken);
previousToken = currentToken;
int factor=10;
if(totalrows>300){
factor =totalrows/300;
}
else
{
factor=1;
}
System.out.println(factor);
int count = 0;
x.setTickLabelRotation(45.0);
System.out.println("This is the property: "+x.isTickLabelsVisible());
x.setTickLabelGap(5000.0);
System.out.println("getTickLabelGap:"+x.getTickLabelGap());
x.setTickLabelGap(10.0);
System.out.println("getTickLabelGap:"+x.getTickLabelGap());
lineChart.getData().addAll(series);
a1.clear();
for(final XYChart.Data<String,Double> data :series.getData()){
data.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
lbl.setText("X :"+data.getXValue()+"\nY :"+ String.valueOf(data.getYValue()));
Tooltip.install(data.getNode(), new Tooltip("X :"+data.getXValue()+"\nY :"+ String.valueOf(data.getYValue())));
}
});
}
cluster.close();
}

Breadth-First Search with obstacles

I'm currently making a game as a school project and I'm trying to figure out the pathfinding of enemies. I've made a basic BFS which works pretty well but does not take in account obstacles, so enemies are stuck by an obstacle is there's one when trying to reach the player. I've tried different things but all I got was null pointer (which I kind of understand, but I don't know how to make this works).
public class BFS {
private Player player;
private Field field;
private Queue<Tile> queue;
private HashMap<Tile, Tile> parents;
private ArrayList<Tile> adjTiles;
public BFS(Player player, Field field) {
this.player = player;
this.field = field;
this.queue = new LinkedList<>();
this.parents = new HashMap<Tile, Tile>();
this.adjTiles = new ArrayList<>();
}
public void lancerBFS() {
int x = player.getIndiceX();
int y = player.getIndiceY();
Tile player = field.getNextTile(y, x);
this.parents.clear();
this.queue.clear();
this.adjTiles.clear();
this.queue.add(field.getNextTile(y, x));
this.parents.put(field.getNextTile(y, x), field.getNextTile(y, x));
while (!queue.isEmpty()) {
Tile temp = queue.remove();
y = temp.getI();
x = temp.getJ();
if (x > 0) {
this.adjTiles.add(field.getNextTile(y, x-1));
}
if (y > 0) {
this.adjTiles.add(field.getNextTile(y-1, x));
}
if (x < 24) {
this.adjTiles.add(field.getNextTile(y, x+1));
}
if (y < 24) {
this.adjTiles.add(field.getNextTile(y+1, x));
}
for (int i = 0 ; i < adjTiles.size() ; i++) {
if (!this.parents.containsKey(adjTiles.get(i))) {
this.parents.put(this.adjTiles.get(i), temp);
this.queue.add(this.adjTiles.get(i));
}
}
this.adjTiles.clear();
}
}
public Tile searchWay(AnimatedEntity entity) {
int x = entity.getIndiceX();
int y = entity.getIndiceY();
Tile t = this.field.getNextTile(y, x);
return this.parents.get(t);
}
public HashMap<Tile, Tile> getParents() {
return parents;
}
}
How I use it (my tiles are 32x32 on a 25x25 map, and enemies move 4 pixels by 4 pixels)
public void moveEnemy(AnimatedEntity e) {
Tile nextTile = this.bfs.searchWay(e);
Tile enemyAt = this.map.getNextTile(e.getIndiceY(), e.getIndiceX());
if (nextTile.getI() == enemyAt.getI() && nextTile.getJ() < enemyAt.getJ()) {
e.moveLeft(entities, inanimatedEntities);
}
if (nextTile.getI() < enemyAt.getI() && nextTile.getJ() == enemyAt.getJ()) {
e.moveUp(entities, inanimatedEntities);
}
if (nextTile.getI() == enemyAt.getI() && nextTile.getJ() > enemyAt.getJ()) {
e.moveRight(entities, inanimatedEntities);
}
if (nextTile.getI() > enemyAt.getI() && nextTile.getJ() == enemyAt.getJ()) {
e.moveDown(entities, inanimatedEntities);
}
}
How enemies get stuck in game:
How enemies get stuck after trying to include isObstacle notion

JavaFX TableView not Updating UI

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.

Resources