Fix spacing between different series in JavaFX line chart - java

I want the item spacing to appear more orderly with a constant spacing
How can I do this? I combed through the JavaFX CSS properties and modena css, and I didn't see anything relevant that seemed to work. I only find these properties:
.chart-legend {
-fx-background-color: white
-fx-background-insets: 0 0 -1 0, 0,1;
-fx-background-radius: 4,4,3;
-fx-padding: 6px;
}

The legend of a Chart is an arbitrary node. It's default implementation is a specialized TilePane, that is its children are sized uniformly across the available width. For a different layout that default implementation can be replaced with a custom legend, f.i. a FlowPane (or HBox).
A quick approach is to subclass LineChart, override updateLegend and replace the default with a custom pane. The example below is dirty in that it relies on implementation details of the default implementation
The custom LineChart:
public static class MyLineChart<X, Y> extends LineChart<X, Y> {
public MyLineChart(Axis<X> xAxis, Axis<Y> yAxis) {
super(xAxis, yAxis);
}
private TilePane legendAlias;
private FlowPane legendReplacement;
#Override
protected void updateLegend() {
// let super do the setup
super.updateLegend();
Node legend = getLegend();
if (legend instanceof TilePane) {
legendAlias = (TilePane) legend;
legendReplacement = new FlowPane(10, 10);
setLegend(legendReplacement);
}
if (legendAlias != null && legendAlias.getChildren().size() > 0) {
legendReplacement.getChildren().setAll(legendAlias.getChildren());
legendAlias.getChildren().clear();
setLegend(legendReplacement);
}
}
}

Related

Dynamically change JavaFX css property

In my FXML I have a simple slider:
<Slider fx:id="timeSlider" showTickLabels="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="4"/>
This slider is part of a desktop music application I am writing and it signifies how far into the song has been played. I have written a change event that is called everytime the the song time is changed, and this successfully accomplishes sliding the knob/thumb down.
Now I am trying to also to color the left part (already played) blue and the right part (yet to be played) white as the knob slides down. If I hard code this line into my CSS i can accomplish fading from blue/white at the 50% mark.
.slider .track {-fx-background-color: linear-gradient(to right, #90C7E0 50%, white 50%);}
However, I need this to be dynamic based on how far I am in the song. I have written the following code, but can't seem to get the CSS style to apply
song.getMediaPlayer().currentTimeProperty().addListener(new ChangeListener<Duration>() {
#Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
if (newValue != null && timeSlider != null) {
timeSlider.setValue(newValue.toSeconds());
double percentage = (newValue.toSeconds()/song.getDuration())*100;
String css = ".slider .track{-fx-background-color: linear-gradient(to right, #90C7E0 " +
percentage + "%, white " + percentage + "%);}";
timeSlider.getStyleClass().add(css);
}
}
});
I believe it's something to do with how I am adding the CSS as a style, because even adding a simple non dynamic style does not work. I get no errors so I am not sure what I am doing wrong.
Any help is appreciated, and better ways to accomplish this are also appreciated. Thanks!
The method getStyleClass() returns a list of CSS class names associated with the node. These class names are used to determine which rules in an external CSS file are applied to the node. You cannot pass CSS selectors and rules in here.
Instead, write an external CSS file which contains a rule for the background color for the track. You can use a "looked-up color" here, which basically works like a "CSS variable". Define the "looked-up color" for the slider, and use it in a rule for the track to set the background color:
style.css:
.slider {
-track-color: white ;
}
.slider .track {
-fx-background-color: -track-color ;
}
Now, in the Java code, you can update the value for the looked-up color by calling setStyle("-track-color: /* someColor */") on the slider. Here is a quick example:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class SliderTest extends Application {
#Override
public void start(Stage primaryStage) {
Slider slider = new Slider();
slider.valueProperty().addListener((obs, oldValue, newValue) -> {
double percentage = 100.0 * (newValue.doubleValue() - slider.getMin()) / (slider.getMax() - slider.getMin());
slider.setStyle("-track-color: linear-gradient(to right, #90C7E0 " + percentage+"%, white "+percentage+("%);"));
});
StackPane root = new StackPane(slider);
root.setPadding(new Insets(20));
Scene scene = new Scene(root);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In your application, you can do the same using the current time of the media player (or just register a listener on the slider's value property, as that is changing too).
With the help of those in the comments I have come up with a solution,
.getStyleClass.add(...) did not do what I originally thought, and I needed to use .setStyle(...) instead however, I needed to set the style on the slider TRACK not the slider itself. Here is my code now...
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
if (newValue != null && timeSlider != null) {
double percentage = (newValue.toSeconds()/song.getDuration())*100;
String cssValue = "-fx-background-color: linear-gradient(to right, #90C7E0 " +
percentage + "%, white " + percentage + "%)";
StackPane sliderTrack = (StackPane) timeSlider.lookup(".track");
sliderTrack.setStyle(cssValue);
timeSlider.setValue(newValue.toSeconds());
}
}

How is it possible to reduce the size of a JavaFX Chart so it fits into a ListCell?

I'm building a JavaFX application and I want to put several very small LineChart into a ListView to get something like this:
What I want it to look like
In my current attempts I've managed to get the charts correctly inside the ListView but so far I've failed to fit them to the desired size: What it currently looks like
Here is my custom ListCell implementation:
//similar to:
//https://stackoverflow.com/questions/31151281/javafx-linechart-as-a-cell-in-treetableview
public class ChartListCell extends ListCell<LineChart.Series> {
private CategoryAxis xAxis = new CategoryAxis();
private NumberAxis yAxis = new NumberAxis();
private LineChart<String, Number> chart = new LineChart<>(xAxis, yAxis);
public ChartListCell() {
//a bunch of visual configuration similar to
//https://stackoverflow.com/questions/41005870/how-to-make-the-chart-content-area-take-up-the-maximum-area-available-to-it
chart.setMaxHeight(17.0);
}
#Override
public void updateItem(XYChart.Series item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
setGraphic(chart);
if (!chart.getData().contains(item))
chart.getData().setAll(item);
}
}
}
CSS styling to remove padding:
.chart{
-fx-padding: 0px;
}
.chart-content{
-fx-padding: 0px;
}
.chart-series-line{
-fx-stroke-width: 2px;
-fx-effect: null;
-fx-stroke: #009682;
}
.axis{
AXIS_COLOR: transparent;
}
.axis:top > .axis-label,
.axis:left > .axis-label {
-fx-padding: 0;
}
.axis:bottom > .axis-label,
.axis:right > .axis-label {
-fx-padding: 0;
}
The cell is bound to an ObservableList<XYChart.Series>.
When I try to call this.setPrefHeight(17); on the cell everything breaks and the Chart is no longer visible.
You can use the snapshot method from the Node class to get a WriteableImage of each chart and show that in your ListCell.

Multiple CssLayouts within VerticalLayout

I have attached two views of my screen, one is the normal view while the other is when I resize my browser window(or when wrapping takes place).I am using Vaadin , I have multiple CssLayouts within a VerticalLayout.
What actually I am doing is I have a UI class extends CssLayout providing general functionality that I have some items in a drop down. When any of the item is selected , a label is added showing that item is selected,having a cross button which removes the label means that the selection is cancelled. The items will be wrapped when reach the end of the width.
I need the above functionality mutiple times so I am placing these CssLayouts in a Vertical layout which is inside a Panel,therefore, when the height of that vertical layout exceeds the panel's height scroll bars will appear which is fine.
Now the issue is the wrapping, as shown in the view.Due to the presence of multiple CssLayouts .What is desired is that when any css layout wraps the layout below moves down so that they'll not overlap ,as they are doing right now.
Code where I am adding these Css Layouts
public class ProcessUserSelectionPanel extends Panel
{
private List<SelectorUI> processUserSelectors = new ArrayList<SelectorUI>();
private static int SELECTION_LAYOUT_HEIGHT = 38;
private static int MAX_PANEL_HEIGHT = 200;
private VerticalLayout innerLayout ;
protected void initUI()
{
addStyleName("u-proc-user-panel");
setHeight(MAX_PANEL_HEIGHT,Component.UNITS_PIXELS);
innerLayout = new VerticalLayout();
innerLayout.addStyleName("u-proc-user-panel");
innerLayout.setWidth(100, Component.UNITS_PERCENTAGE);
innerLayout.setMargin(true);
setContent(innerLayout);
}
public void updateSelectorPanels(){
innerLayout.removeAllComponents();
processUserSelectors.clear();
int task = 0;
for(int i = 0 ; i < 5 ; i++){
SelectorUI processUserSelector = new SelectorUI();
processUserSelectors.add(processUserSelector);
innerLayout.addComponent(processUserSelector);
task++;
}
}
innerLayout.setHeight((task+1)*SELECTION_LAYOUT_HEIGHT,Component.UNITS_PIXELS );
if((task+1)*SELECTION_LAYOUT_HEIGHT < MAX_PANEL_HEIGHT){
setHeight((task+1)*SELECTION_LAYOUT_HEIGHT+5, Component.UNITS_PIXELS);
}
else{
setHeight(MAX_PANEL_HEIGHT,Component.UNITS_PIXELS);
}
}
Css classes I have used for the label ( having text and cross icon) inside Css layout
.u-selector-panel-big-label{
margin-right:5px;
margin-bottom:5px;
display:inline-block;
border:solid;
border-width:thin;
border-color:rgb(216, 216, 216);
background-color: #EBE2FF;/*#D0C4F0; */
pointer-events: none;
}
.u-selector-panel-heading{
margin-right:20px;
text-align: center;
white-space:nowrap;
display:inline-block;
/* pointer-events: none; */
width:150px;
}
.u-selector-panel-text-label{
margin-right:15px;
text-align: center;
white-space:nowrap;
display:inline-block;
background-color: #EBE2FF;/*#D0C4F0; */
pointer-events: none;
}
.u-selector-panel-icon-label{
text-align: center;
white-space:nowrap;
display:inline-block;
background-color: #EBE2FF;
pointer-events: all;
}
.v-csslayout-container .v-filterselect {
margin-top: 2px;
display:inline-flex;
height: 1.49em;
}
May be I am missing something or not using these correctly.Any help to solve this issue is appreciated.
Cheers
Try giving the width in percentage instead of pixels.
There is an addon called Tokenfield which does solve the functionality you are looking at. Why don't you have look at it? It works great and awesome. Try it if doesn't solve your problem, just let me I will have a look in to it.

JavaFX TableColumn Graphic not Hiding

I'm creating a custom header for my TableColumns that is the label of the column plus a TextField that will allow users to perform searches. I'm setting the column headers like so:
getColumns().addListener(new ListChangeListener<TableColumn<S, ?>>() {
#Override
public void onChanged(final ListChangeListener.Change<? extends TableColumn<S, ?>> change) {
while (change.next()) {
Label label;
TextField search;
VBox graphic;
for (TableColumn<S, ?> column : change.getAddedSubList()) {
label = new Label(column.getText());
search = new TextField();
graphic = new VBox();
graphic.getStyleClass().add("k-column-graphic");
graphic.getChildren().addAll(label, search);
column.setGraphic(graphic);
}
}
}
});
So the column's graphic is what is displayed. I'm using the following CSS (the graphic itself has a "k-column-graphic" CSS class, while the TableView has a "k-table-view" CSS class)
/** Hide default text label in KTableView */
.k-table-view .column-header > .label {
-fx-content-display: graphic-only;
}
.k-column-graphic {
-fx-alignment: center-left;
-fx-spacing: 5;
-fx-padding: 2;
}
This works great, but I'm also allowing the columns to be hidden by enabling the TableView.setTableMenuButtonVisible(true); property, which adds a button to easily hide columns.
Whenever I try to hide a column, it hides successfully, but the graphic (the Label/TextField) remain. Both seem to have a width of 0 or 1, and are very small, but you can still see them.
How, either through CSS or somewhere in my code, do I make it to where the graphic Node for the TableColumn will hide as well?
When you toggle the CheckMenuItem to show/hide the column, your customized controls won't automatically change their values of VisibleProperty. So what you need to do is simply bind the VisibleProperty of your own controls to the TableColumn's VisibleProperty.
Following sample is based on your code. Hoping it can help.
getColumns().addListener(new ListChangeListener<TableColumn<S, ?>>() {
#Override
public void onChanged(final ListChangeListener.Change<? extends TableColumn<S, ?>> change) {
while (change.next()) {
Label label;
TextField search;
VBox graphic;
for (TableColumn<S, ?> column : change.getAddedSubList()) {
label = new Label(column.getText());
search = new TextField();
graphic = new VBox();
graphic.getStyleClass().add("k-column-graphic");
graphic.getChildren().addAll(label, search);
column.setGraphic(graphic);
/* ======= add the following two lines ============== */
label.visibleProperty().bind(column.visibleProperty());
search.visibleProperty().bind(column.visibleProperty());
}
}
}
});

Binding two tableviews together such that they scroll in sync

I want to bind two tableviews together such that they scroll in sync. How do I do that? I am unable to find out how to access the scrollbar of a tableview.
I've made a CSS hack to bind a Tableview with an external scrollbar. One scrollbar controls both tableviews.
An overview of my idea:
Create two tableviews
Make one Vertical scrollbar. Let's call it myScrollbar in this example
Set the min and max of myScrollbar to size of min=0, max=TableView.Items.size()
When the value of myScrollbar changes then call both tableview's scrollTo(int) function
Disable the native vertical scrollbar of the tableview implemented with CSS.
This will give you two tables, both controlled by one external scrollbar (myScrollbar).
Here is the code to hide the scrollbar of a tableview using css:
/* The main scrollbar **track** CSS class */
.mytableview .scroll-bar:vertical .track{
-fx-padding:0px;
-fx-background-color:transparent;
-fx-border-color:transparent;
-fx-background-radius: 0em;
-fx-border-radius:2em;
}
/* The increment and decrement button CSS class of scrollbar */
.mytableview .scroll-bar:vertical .increment-button ,
.mytableview .scroll-bar:vertical .decrement-button {
-fx-background-color:transparent;
-fx-background-radius: 0em;
-fx-padding:0 0 0 0;
}
.mytableview .scroll-bar:vertical .increment-arrow,
.mytableview .scroll-bar:vertical .decrement-arrow
{
-fx-shape: " ";
-fx-padding:0;
}
/* The main scrollbar **thumb** CSS class which we drag every time (movable) */
.mytableview .scroll-bar:vertical .thumb {
-fx-background-color:transparent;
-fx-background-insets: 0, 0, 0;
-fx-background-radius: 2em;
-fx-padding:0px;
}
Then we need to set how to scroll the tableview by using the scrollbar.
scroll.setMax(100); //make sure the max is equal to the size of the table row data.
scroll.setMin(0);
scroll.valueProperty().addListener(new ChangeListener(){
#Override
public void changed(ObservableValue ov, Number t, Number t1) {
//Scroll your tableview according to the table row index
table1.scrollTo(t1.intValue());
table2.scrollTo(t1.intValue());
}
});
http://blog.ngopal.com.np/2012/09/25/how-to-bind-vertical-scroll-in-multi-tableview/
I don't think this is currently possible. TableViewSkin inherits from VirtualContainerBase which has a VirtualFlow field. The VirtualFlow object has two VirtualScrollBar fields, hbar and vbar which is what you're after. I can't see any way of getting to it though.
Interestingly, there is also a private contentWidth field in TableView although this is private. I'm sure that the JFX team are being ultra cautious about opening up too much of the API which is understandable. You could ask to get the contentWidth field opened up as an int property as a feature requestion on the JFX JIRA or openjfx-dev mailing list.
A stop gap measure would be to bind the selected item or index property of the table views selection model.
The easiest way I've found to solve the problem is to bind the valueProperty of the visible an the hidden scrollbars.
// Controller
#FXML private TableView<MyBean> tableLeft;
#FXML private TableView<MyBean> tableRight;
#FXML private ScrollBar scrollBar;
#SuppressWarnings("rawtypes")
private void bindScrollBars(TableView<?> tableView1, TableView<?> tableView2,
ScrollBar scrollBar, Orientation orientation) {
// Get the scrollbar of first table
VirtualFlow vf = (VirtualFlow)tableView1.getChildrenUnmodifiable().get(1);
ScrollBar scrollBar1 = null;
for (final Node subNode: vf.getChildrenUnmodifiable()) {
if (subNode instanceof ScrollBar &&
((ScrollBar)subNode).getOrientation() == orientation) {
scrollBar1 = (ScrollBar)subNode;
}
}
// Get the scrollbar of second table
vf = (VirtualFlow)tableView2.getChildrenUnmodifiable().get(1);
ScrollBar scrollBar2 = null;
for (final Node subNode: vf.getChildrenUnmodifiable()) {
if (subNode instanceof ScrollBar &&
((ScrollBar)subNode).getOrientation() == orientation) {
scrollBar2 = (ScrollBar)subNode;
}
}
// Set min/max of visible scrollbar to min/max of a table scrollbar
scrollBar.setMin(scrollBar1.getMin());
scrollBar.setMax(scrollBar1.getMax());
// bind the hidden scrollbar valueProterty the visible scrollbar
scrollBar.valueProperty().bindBidirectional(scrollBar1.valueProperty());
scrollBar.valueProperty().bindBidirectional(scrollBar2.valueProperty());
}
/*
* This method must be called in Application.start() after the stage is shown,
* because the hidden scrollbars exist only when the tables are rendered
*/
public void setScrollBarBinding() {
bindScrollBars(this.tableLeft, this.tableRight, this.scrollBar, Orientation.VERTICAL);
}
Now you have to call the binding from Application after the stage is shown and the tables are rendered:
// Application
private MyController controller;
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(SalesApp.class.getResource("scene.fxml"));
BorderPane root = (BorderPane) fxmlLoader.load();;
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("app.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
controller = (MyController) fxmlLoader.getController();
controller.setScrollBarBinding();
} catch(Exception e) {
e.printStackTrace();
}
}
Now the tables should scroll synchronously via mouse, key, or scrollbar.
Have fun, Olaf

Categories

Resources