I'd using JavaFX to draw barcharts which represents a sound power. The range of my values are from -60 to +3 dbBF (decibel full scale).
When JavaFX draw the "bars", the baseline is always set to 0 so, my bars are drawn from 0 to +X (above the zero line) or from 0 to -X (below the zero line).
My aim is to get a bar from -60 to -X. Is there a way to set the baseline origin to -60 ? I looked for tips on google and tried few things but nothing worked.
Thank you for your answers.
I'm not aware of a way to change the baseline of a JavaFX chart. However, you can make it look like it's starting from -60. The ValueAxis (and by extension, NumberAxis) class has a property for formatting the tick labels: tickLabelFormatter. You can supply your own StringFormatter that makes the labels "off by 60". Here's an example:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class App extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
var scene = new Scene(createChart() 600, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("SO-53959031");
primaryStage.show();
}
#SuppressWarnings("unchecked")
private Series<String, Number> createData() {
return new Series<>(FXCollections.observableArrayList(
new Data<>("Test #0", 40),
new Data<>("Test #1", 20),
new Data<>("Test #2", 63),
new Data<>("Test #3", 50),
new Data<>("Test #4", 30)
));
}
private BarChart<String, Number> createChart() {
var yAxis = new NumberAxis("dbBF", 0, 63, 1);
yAxis.setTickLabelFormatter(new StringConverter<>() {
#Override
public String toString(Number object) {
return Double.toString(object.doubleValue() - 60);
}
#Override
public Number fromString(String string) {
return Double.valueOf(string) + 60;
}
});
var chart = new BarChart<>(new CategoryAxis(), yAxis);
chart.getData().add(createData());
chart.setLegendVisible(false);
return chart;
}
}
There's a caveat: You have to translate your data to be zero-based. This only has to be done for the chart's data. In other words, your model can keep the data between -60 and 3 but it has to be translated to be in the range 0 to 63 for the chart.
Screenshot of example:
Related
I'm writing a rudimentary Candlestick chart class in which the candlesticks are created as Regions and are plotted by setting their layoutX and layoutY values to the getDisplayPosition() of the relevant axis.
For example, to plot a candlestick at value 3 on an X axis, I do this:
candlestick.setLayoutX(xAxis.getDisplayPosition(3));
When the stage resizes or when the axes are zoomed in or out, the candlesticks' layout values have to be reset so that the chart renders correctly. I'm currently handling this via ChangeListeners for resize events and Button.setOnAction()s for zooming.
However, I'd rather bind the candlesticks' layout properties to the axes' display positions than set/reset the layout values, but can't find a "displayPositionProperty" (or similar) for a NumberAxis.
Is it possible to do this? Which NumberAxis property would I bind to? ie.
candlestick.layoutXProperty().bind(xAxis.WHICH_PROPERTY?);
Also, would binding the properties be more efficient than resetting layout positions? Some of the charts could potentially have thousands of candlesticks but I can't test resource usage until I figure out how to code the bind.
I've experimented with scaling the candlesticks to the axes' scale but can't use that approach because scaling a Region affects its border width. For certain types of candlesticks, that can change its meaning.
I've also played with the Ensemble candlestick demo chart. It was useful in giving me a start but is too simplistic for my needs.
Here's a MVCE that demonstrates my approach. Any guidance re binding would be very much appreciated.
I'm using OpenJFX 17.
package test023;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Test023 extends Application {
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
Pane pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane bp = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: black;");
pChart.getChildren().add(point);
//Plot the point in its initial position (value 3 on the axis)
double pointXValue = 3D;
plotPoint(point, pointXValue, xAxis);
//*****************************************************************
//Can the listeners and button.setOnActions be replaced by binding
//the point's layout value to the axis display position?
//*****************************************************************
//Handle resize events
pChart.widthProperty().addListener((obs, oldVal, newVal) -> {
plotPoint(point, pointXValue, xAxis);
});
stage.maximizedProperty().addListener((obs, oldVal, newVal) -> {
plotPoint(point, pointXValue, xAxis);
});
//Handle zooming (hard-coded upper and lower bounds for the
//sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
xAxis.layout();
plotPoint(point, pointXValue, xAxis);
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
xAxis.layout();
plotPoint(point, pointXValue, xAxis);
});
bp.setCenter(vb);
bp.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(bp));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
private void plotPoint(Region region, double axisPos, NumberAxis axis) {
Platform.runLater(() -> {
double posX = axis.getDisplayPosition(axisPos);
region.setLayoutX(posX);
region.setLayoutY(80D);
});
}
public static void main(String args[]){
launch(args);
}
}
+1 for #LukasOwen answer which answer you actual question related to bindings.
But as you are aware that every problem has more than one approach, I am suggesting mine, considering the scalability (adding many points) and too many bindings (for every point).
The key things in this approach are:
You add all your points numbers and its node to a map.
Every time the xAxis is rendered, you update the all the points position. So this will be implicitly done if you resize, change range, or maximize the window.
Below is the example of the approach:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
public class Test023 extends Application {
Map<Double, Region> plotPoints = new HashMap<>();
double yOffset = 80D;
Pane pChart;
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
xAxis.needsLayoutProperty().addListener((obs, old, needsLayout) -> {
if(!needsLayout) {
plotPoints.forEach((num, point) -> {
double posX = xAxis.getDisplayPosition(num);
point.setLayoutX(posX);
point.setLayoutY(yOffset);
});
}
});
pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane root = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
addPoint(3D, "black");
addPoint(4D, "red");
addPoint(5D, "blue");
//Handle zooming (hard-coded upper and lower bounds for the sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
});
root.setCenter(vb);
root.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(root));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
private void addPoint(double num, String color) {
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: " + color);
plotPoints.put(num, point);
pChart.getChildren().add(point);
}
public static void main(String args[]) {
launch(args);
}
}
Something like this would work
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
Pane pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane bp = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: black;");
pChart.getChildren().add(point);
//Plot the point in its initial position (value 3 on the axis)
double pointXValue = 3D;
point.setLayoutY(80D);
point.layoutXProperty().bind(Bindings.createDoubleBinding(()-> {
return xAxis.getDisplayPosition(pointXValue);
}, xAxis.lowerBoundProperty(), xAxis.upperBoundProperty(), pChart.widthProperty()));
//Handle zooming (hard-coded upper and lower bounds for the
//sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
xAxis.layout();
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
xAxis.layout();
});
bp.setCenter(vb);
bp.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(bp));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
This creates a custom double binding with a function that calculates the value of the binding every time the dependencies are changed, see createDoubleBinding​ for more info.
Up from a certain number of plotted ticks the labels on the xAxis are rotated by 90 degrees into a vertical position.
I would like to keep them horizontal and only plot as many as are fitting to the xAxis.
This shows how it looks and how I would like it to look:
Trying xAxis.setTickLabelRotation() is not showing any effect if there is not enough space to rotate the labels it seems.
I did a work around hiding the xAxis labels, positioning a HBox below the chart and fill it with a few labels that are horizontal. But that is no clean solution and did lead to other issues I would like to avoid.
This example:
JavaFX manually set NumberAxis ticks
shows how to subclass a NumberAxis but I did not make it yet to transfer that to achieve my goal. My idea is to subclass the NumberAxis, overwrite the methods that are responsible for plotting the labels on the xAxis and use a formatter that turns the numbers into the date format to be plotted.
Would that be the right approach and what would be the methods to address? I spent a lot of time going through the sources without success. Any hint into the right direction would be highly appreciated.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class LineChartSample extends Application {
#Override
public void start(Stage stage) {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis);
lineChart.setAnimated(false);
lineChart.setCreateSymbols(false);
XYChart.Series series = new XYChart.Series();
for (int i = 0; i < 100; i+=1)
series.getData().add(new XYChart.Data(msToDateString(i), Math.random()));
Scene scene = new Scene(lineChart, 800, 150);
lineChart.getData().add(series);
stage.setScene(scene);
stage.show();
}
//msToDateString-----------------------------------------------------------------
private static DateFormat df = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS");
private static Date result = new Date();
public static String msToDateString(long milliseconds) {
result.setTime(milliseconds);
return df.format(result);
}
public static void main(String[] args) {
launch(args);
}
}
I know this is an old topic now, but I came across this question while trying to deal with the similar problem myself. I was using custom LineChart class (extended) and had to override layoutPlotChildren() method to be able to put additional shapes to the plot list. To make graph more readable, I wanted to show every second tick on the X axis. In the end, I solved this problem by iterating tick marks and making every second mark invisible. Here is the snippet of the code:
public class MyLineChart extends LineChart<String, Double> {
public MyLineChart (
#NamedArg("xAxis") final Axis<String> xAxis,
#NamedArg("yAxis") final Axis<Double> yAxis) {
super(xAxis, yAxis);
}
...
#Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
...
ObservableList<Axis.TickMark<String>> tickMarks = getXAxis().getTickMarks();
for (int i = 0; i < tickMarks.size(); i++) {
tickMarks.get(i).setTextVisible(i % 2 == 0);
}
}
Hope this helps...
I made this code that creates a scatter chart and allows me to change the color of a node on the plot when I click/select it.
package com.jpc.javafx.charttest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CreateChart extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
//-------Create Chart--------------
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
XYChart.Series<Number,Number> dataSeries1 = new XYChart.Series();
ScatterChart chart = new ScatterChart(xAxis,yAxis);
dataSeries1.getData().add(new XYChart.Data( 1, 567));
dataSeries1.getData().add(new XYChart.Data( 5, 612));
dataSeries1.getData().add(new XYChart.Data(10, 800));
chart.getData().add(dataSeries1);
//-----Select node and change color -----
for(final XYChart.Data<Number,Number> data : dataSeries1.getData()) {
data.getNode().setOnMouseClicked(e-> {
//dataSeries1.getNode().lookup(".chart-symbol").setStyle("-fx-background-color: red"); that does not work
data.getNode().setStyle("-fx-background-color: blue" );
});
}
VBox vbox = new VBox(chart);
Scene scene = new Scene(vbox, 400, 200);
primaryStage.setScene(scene);
primaryStage.setHeight(300);
primaryStage.setWidth(1200);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
The problem is that when I select another point the previous one stays blue. So I need to reset all the nodes to the default color before I change the selected point's color.
I tried to add this:
dataSeries1.getNode().lookup(".chart-symbol").setStyle("-fx-background-color: red");
but I get:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
To summarize your requirement:
a visual property of a chart-symbol should be marked on user interaction
there should be only one such marked symbol
Sounds like a kind of selection mechanism - which is not supported for chart symbols out of the box, application code must take care of it. The task is
keep track of the (last) selected symbol
guarantee that at any time only a single symbol is selected
keep the visual state of un/selected as needed
The most simple implementation for the logic (the first two bullets) would be to keep a reference to the current selected and update it on user interaction. An appropriate instrument for the latter would be a PseudoClass: can be defined in the css and de/activated along with the logic.
Code snippets (to be inserted into your example)
// Pseudo-class
private PseudoClass selected = PseudoClass.getPseudoClass("selected");
// selected logic
private Node selectedSymbol;
protected void setSelectedSymbol(Node symbol) {
if (selectedSymbol != null) {
selectedSymbol.pseudoClassStateChanged(selected, false);
}
selectedSymbol = symbol;
if (selectedSymbol != null) {
selectedSymbol.pseudoClassStateChanged(selected, true);
}
}
// event handler on every symbol
data.getNode().setOnXX(e -> setSelectedSymbol(data.getNode()));
css example, to be loaded via a style-sheet f.i.:
.chart-symbol:selected {
-fx-background-color: blue;
}
One thing you can do is loop through the data and change the color for the one clicked and set all the other to null
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CreateChart extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
//-------Create Chart--------------
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
XYChart.Series<Number,Number> dataSeries1 = new XYChart.Series();
ScatterChart chart = new ScatterChart(xAxis,yAxis);
dataSeries1.getData().add(new XYChart.Data( 1, 567));
dataSeries1.getData().add(new XYChart.Data( 5, 612));
dataSeries1.getData().add(new XYChart.Data(10, 800));
chart.getData().add(dataSeries1);
//-----Select node and change color -----
for(final XYChart.Data<Number,Number> data : dataSeries1.getData()) {
data.getNode().setOnMouseClicked(e-> {
for(final XYChart.Data<Number,Number> data2 : dataSeries1.getData()) {
if(data == data2)
{
data2.getNode().setStyle("-fx-background-color: blue" );
}
else
{
data2.getNode().setStyle(null);
}
}
});
}
VBox vbox = new VBox(chart);
Scene scene = new Scene(vbox, 400, 200);
primaryStage.setScene(scene);
primaryStage.setHeight(300);
primaryStage.setWidth(1200);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
In my JavaFX app I have a TableView with multiple columns, one of which displays data in a graphical form. To do this I have created a CanvasCell object that creates and manages its own Canvas to deal with the drawing. The drawing part works just fine.
I'd now like to put Tooltips over some regions within the Canvas/Cell. There may be multiple Tooltips per Cell (which prevents me from adding the Tooltip at the Cell level) and they should only trigger in specific regions of the graph. However, I'm not managing to get it functioning at all. I don't seem to understand the interactions of Display Node hierarchy well enough (read "at all") to be able to place the Tooltip anywhere where it will actually work.
Documentation for JavaFX is sparse and Google + SO has come up blank for all searches that I've tried. Is there anyone who knows how to do this sort of thing or should I just write it off as "not an option" for now.
For info, the CanvasCell calls a draw() function inside an extended Canvas object on updateItem(). The code in which I've tried to create a Tooltip sits inside that draw() function and looks like:
Rectangle rect = new Rectangle(leftVal, topVal, width, height);
gc.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
Tooltip tooltip = new Tooltip("Tooltip Text");
Tooltip.install(rect, tooltip);
but that code was written more in hope than anything else and doesn't generate anything useful in the interface.
If someone can point me in the right direction, I will be very grateful.
If you don't need the timing control illustrated here, you can simply install the Tooltip on the enclosing Canvas and leverage Shape::contains to condition the text as shown below.
node.setOnMouseMoved(e -> {
tooltips.forEach((color, bounds) -> {
if (bounds.contains(e.getX(), e.getY())) {
tooltip.setText(color.toString());
}
});
});
As suggested here, Java 9 and later provide control over Tooltip timing via the properties showDelay and showDuration.
A similar approach is illustrated here for Swing.
import javafx.application.Application;
import javafx.scene.shape.Rectangle;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
/**
* #see https://stackoverflow.com/a/53785468/230513
* #see https://stackoverflow.com/a/53753537/230513
*/
public class CanvasTooltipDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
Scene sc = new Scene(root, 400, 400);
stage.setScene(sc);
Canvas canvas = new Canvas(200, 200);
root.getChildren().add(canvas);
Map<Color, Rectangle> tooltips = new HashMap<>();
tooltips.put(Color.RED, new Rectangle(0, 0, 100, 100));
tooltips.put(Color.BLUE, new Rectangle(100, 0, 100, 100));
tooltips.put(Color.YELLOW, new Rectangle(0, 100, 100, 100));
tooltips.put(Color.GREEN, new Rectangle(100, 100, 100, 100));
GraphicsContext gc = canvas.getGraphicsContext2D();
tooltips.forEach((color, bounds) -> {
gc.setFill(color);
gc.fillRect(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
});
setToolTips(canvas, tooltips);
stage.show();
}
private void setToolTips(Node node, Map<Color, Rectangle> tooltips) {
Tooltip tooltip = new Tooltip();
Tooltip.install(node, tooltip);
node.setOnMouseMoved(e -> {
tooltips.forEach((color, bounds) -> {
if (bounds.contains(e.getX(), e.getY())) {
tooltip.setText(color.toString());
}
});
});
node.setOnMouseExited(e -> {
tooltip.hide();
});
}
public static void main(String[] args) {
Application.launch(args);
}
}
I have the same solution as per #Slaw suggested. My idea is to make it more centralized so that you can pass your node and its regions you want to show the tooltips.
In the below demo, you can use the setToolTips() as static utitlity method for multiple nodes.
Note: some part of the logic is referred from Tooltip core implementation ;)
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.HashMap;
import java.util.Map;
public class MultiTooltipDemo extends Application {
private double lastMouseX;
private double lastMouseY;
private static int TOOLTIP_XOFFSET = 10;
private static int TOOLTIP_YOFFSET = 7;
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
Scene sc = new Scene(root, 600, 600);
stage.setScene(sc);
stage.show();
StackPane box1 = new StackPane();
box1.setMaxSize(200, 200);
box1.setStyle("-fx-background-color:red, blue, yellow, green; -fx-background-insets: 0 100 100 0, 0 0 100 100, 100 100 0 0, 100 0 0 100;");
root.getChildren().add(box1);
Map<String, Rectangle2D> tooltips = new HashMap<>();
tooltips.put("I am red", new Rectangle2D(0, 0, 100, 100));
tooltips.put("I am blue", new Rectangle2D(100, 0, 100, 100));
tooltips.put("I am yellow", new Rectangle2D(0, 100, 100, 100));
tooltips.put("I am green", new Rectangle2D(100, 100, 100, 100));
setToolTips(box1, tooltips);
}
private void setToolTips(Node node, Map<String, Rectangle2D> tooltips) {
Duration openDelay = Duration.millis(1000);
Duration hideDelay = Duration.millis(5000);
Tooltip toolTip = new Tooltip();
Timeline hideTimer = new Timeline();
hideTimer.getKeyFrames().add(new KeyFrame(hideDelay));
hideTimer.setOnFinished(event -> {
toolTip.hide();
});
Timeline activationTimer = new Timeline();
activationTimer.getKeyFrames().add(new KeyFrame(openDelay));
activationTimer.setOnFinished(event -> {
Bounds nodeScreenBounds = node.localToScreen(node.getBoundsInLocal());
double nMx = nodeScreenBounds.getMinX();
double nMy = nodeScreenBounds.getMinY();
toolTip.setText("");
tooltips.forEach((str, bounds) -> {
double mnX = nMx + bounds.getMinX();
double mnY = nMy + bounds.getMinY();
double mxX = mnX + bounds.getWidth();
double mxY = mnY + bounds.getHeight();
if (lastMouseX >= mnX && lastMouseX <= mxX && lastMouseY >= mnY && lastMouseY <= mxY) {
toolTip.setText(str);
}
});
if (!toolTip.getText().isEmpty()) {
toolTip.show(node.getScene().getWindow(), lastMouseX + TOOLTIP_XOFFSET, lastMouseY + TOOLTIP_YOFFSET);
hideTimer.playFromStart();
}
});
node.setOnMouseMoved(e -> {
double buffPx = 2;
double eX = e.getScreenX();
double eY = e.getScreenY();
// Not hiding for slight mouse movements while tooltip is showing
if (hideTimer.getStatus() == Animation.Status.RUNNING) {
if (lastMouseX - buffPx <= eX && lastMouseX + buffPx >= eX && lastMouseY - buffPx <= eY && lastMouseY + buffPx >= eY) {
return;
}
}
lastMouseX = e.getScreenX();
lastMouseY = e.getScreenY();
toolTip.hide();
hideTimer.stop();
activationTimer.playFromStart();
});
node.setOnMouseExited(e -> {
toolTip.hide();
activationTimer.stop();
hideTimer.stop();
});
}
public static void main(String[] args) {
Application.launch(args);
}
}
I created a Bar char but there is a big gap between the bars (I think it's because I use one element in each series). I tried setBarGap() but it doesn't change anything. here is what i do : http://i.imgur.com/k3ZEYCT.png? and here is an example of what i want : http://i.imgur.com/fb7tMx5.png
ObservableList<XYChart.Series<String, Number>> answer = FXCollections.observableArrayList();
for(int i=1;i<9;i++){
....
...
...
XYChart.Series<String, Number> series6 = new XYChart.Series<String, Number>();
series6.getData().add(new XYChart.Data(String.valueOf(i), 2*i));
answer.add(series6);
}
XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
series1.getData().add(new Data<String, Number>(">8",rs2.getInt(1)));
answer.add(series1);
return answer;
The problem is you add more than one category for all data series. If one data series is named "1", and the other is named "2" and these are added to different series then the layout needs to save room in each category's group of bars for the missing category. Like there's a space in "2" group for "1" bars, but you haven't added any.
Make sure you never add more than one category - they all have to have the same value. Since it seems you're using different series for the color and legend, then just don't name the categories.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Side;
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.stage.Stage;
public class BarChartSample extends Application {
#Override public void start(Stage stage) {
stage.setTitle("Bar Chart Sample");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final BarChart<String,Number> bc = new BarChart(xAxis,yAxis);
bc.setTitle("Frequency of visit");
bc.setLegendSide(Side.RIGHT);
//if you want axis labels
xAxis.setLabel("");
yAxis.setLabel("");
bc.getData().addAll(makeSerie("daily",20),
makeSerie("twice a week",30),
makeSerie("weekly",40),
makeSerie("monthly",50));
xAxis.setVisible(false);
xAxis.setTickMarkVisible(false);
xAxis.setTickLabelsVisible(false);
bc.setBarGap(20);
Scene scene = new Scene(bc);
stage.setScene(scene);
stage.show();
}
//just to save typing
private XYChart.Series<String, Number> makeSerie(String name, Number value){
return new XYChart.Series(name,
FXCollections.observableArrayList(new XYChart.Data("", value)));
}
public static void main(String[] args) {
launch(args);
}
}