I'm using XYChart in JavaFX 8 and I would like to display gaps for empty cells for the specified series. When I passed null value then I got NullPointerException:
series.get(index).getData().add(new XYChart.Data<>(Key, null));
I also found the bug https://bugs.openjdk.java.net/browse/JDK-8092134 describing this problem, but I don't know is it still actual.
Does anyone know how to resolve this problem?
Best regards,
Michael
As it is quite evident that, this feature is not included.. and if you are very desperate to get this behavior, you can try the below logic.
Having said that, there can be many better ways, but this answer is to give you some initial idea about how you can tweak the current implementation using the protected methods of the chart.
The idea is.. once the plot children layout is done, we recompute the logic of rendering the line path.. and remove the unwanted data points. And as mentioned, this is just for idea purpose only, if you have more data series, then you may need to work accordingly.
[UPDATE] : If you want the paths/data points for each series, append the ".series" to the ".chart-series-line" and ".data" style classes.
Please check the below demo:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.layout.VBox;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.stage.Stage;
import java.util.*;
public class XYChartDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox();
CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("days");
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("USD");
// AS OF NOW THIS IS THE CORE POINT I AM RELYING ON !! YOU CAN THINK OF A BETTER APPROACH TO IDENTIFY THE GAP POINTS.
List<Integer> s0GapIndexes = Arrays.asList(3, 7);
List<Integer> s1GapIndexes = Arrays.asList(4, 8);
Map<Integer, List<Integer>> seriesGap = new HashMap<>();
seriesGap.put(0, s0GapIndexes);
seriesGap.put(1, s1GapIndexes);
XYChart.Series<String, Double> series0 = new XYChart.Series<>();
series0.getData().add(new Data<>("2000-01-01", 13.2));
series0.getData().add(new Data<>("2000-01-02", 10.1));
series0.getData().add(new Data<>("2000-01-03", 14.1));
series0.getData().add(new Data<>("2000-01-04", 0.0)); // gap (INDEX 3)
series0.getData().add(new Data<>("2000-01-05", 6.3));
series0.getData().add(new Data<>("2000-01-06", 9.82));
series0.getData().add(new Data<>("2000-01-07", 12.82));
series0.getData().add(new Data<>("2000-01-08", 0.0)); // gap (INDEX 7)
series0.getData().add(new Data<>("2000-01-09", 4.82));
series0.getData().add(new Data<>("2000-01-10", 8.82));
series0.getData().add(new Data<>("2000-01-11", 8.82));
XYChart.Series<String, Double> series1 = new XYChart.Series<>();
series1.getData().add(new Data<>("2000-01-01", 20.2));
series1.getData().add(new Data<>("2000-01-02", 14.1));
series1.getData().add(new Data<>("2000-01-03", 7.1));
series1.getData().add(new Data<>("2000-01-04", 9.0));
series1.getData().add(new Data<>("2000-01-05", 0.0)); // gap (INDEX 4)
series1.getData().add(new Data<>("2000-01-06", 5.32));
series1.getData().add(new Data<>("2000-01-07", 11.0));
series1.getData().add(new Data<>("2000-01-08", 15.3));
series1.getData().add(new Data<>("2000-01-09", 0.0)); // gap (INDEX 8)
series1.getData().add(new Data<>("2000-01-10", 4.82));
series1.getData().add(new Data<>("2000-01-11", 6.82));
CustomLineChart lineChart = new CustomLineChart(xAxis, yAxis, seriesGap);
lineChart.getData().addAll(series0, series1);
root.getChildren().addAll(lineChart);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
class CustomLineChart<X, Y> extends LineChart<X, Y> {
Map<Integer, List<Integer>> seriesGap;
public CustomLineChart(Axis<X> xAxis, Axis<Y> yAxis, Map<Integer, List<Integer>> seriesGap) {
super(xAxis, yAxis);
this.seriesGap = seriesGap;
}
#Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
updatePath();
updateDataPoints();
}
private void updatePath() {
seriesGap.forEach((seriesNo, gapIndexes) -> {
Path path = (Path) lookup(".chart-series-line.series" + seriesNo);
System.out.println(path);
if (!path.getElements().isEmpty()) {
int dataSize = getData().get(seriesNo).getData().size();
int pathEleSize = path.getElements().size();
// Just ensuring we are dealing with right path
if (pathEleSize == dataSize + 1) {
// Build a new path, by jumping the gap points
List<PathElement> newPath = new ArrayList<>();
newPath.add(path.getElements().get(0));
for (int i = 1; i < path.getElements().size(); i++) {
if (gapIndexes.contains(i - 1)) {
LineTo lt = (LineTo) path.getElements().get(i + 1);
newPath.add(new MoveTo(lt.getX(), lt.getY()));
} else {
newPath.add(path.getElements().get(i));
}
}
// Update the new path to the current path.
path.getElements().clear();
path.getElements().addAll(newPath);
}
}
});
}
private void updateDataPoints() {
Group plotContent = (Group) lookup(".plot-content");
seriesGap.forEach((seriesNo, gapIndexes) -> {
// Remove all data points at the gap indexes
gapIndexes.forEach(i -> {
Node n = lookup(".series" + seriesNo + ".data" + i);
if (n != null) {
plotContent.getChildren().remove(n);
}
});
});
}
}
}
Related
I am in the process of creating a line chart in JavaFX. All is good currently and it successfully creates the chart with the data I need from a database stored procedure. Anyway what I require if possible is for every data point on the LineChart to have a mouse hover event on it which states the value behind the specific point, for example £150,000. I have seen examples of this been done on PieCharts where it shows the % value on hover but I cannot find examples anywhere for LineCharts, can this even be done?
Can anyone point me in the right direction if possible?
Code so far:
private static final String MINIMIZED = "MINIMIZED";
private static final String MAXIMIZED = "MAXIMIZED";
private static String chartState = MINIMIZED;
// 12 Month Sales Chart
XYChart.Series<String, Number> series = new XYChart.Series<>();
XYChart.Series<String, Number> series2 = new XYChart.Series<>();
public void getDeltaData() {
try {
Connection con = DriverManager.getConnection(connectionUrl);
//Get all records from table
String SQL = "";
Statement stmt = con.createStatement();
//Create the result set from query execution.
ResultSet rs = stmt.executeQuery(SQL);
while (rs.next()) {
series.getData().add(new XYChart.Data<String, Number>(rs.getString(1),
Double.parseDouble(rs.getString(7))));
series2.getData().add(new XYChart.Data<String, Number>(rs.getString(1),
Double.parseDouble(rs.getString(8))));
}
rs.close();
stmt.close();
} catch (Exception e) {
}
yearChart = createChart();
}
protected LineChart<String, Number> createChart() {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
// setup chart
series.setName("Target");
series2.setName("Actual");
xAxis.setLabel("Period");
yAxis.setLabel("£");
yearChart.getData().add(series);
yearChart.getData().add(series2);
yearChart.setCreateSymbols(false);
return yearChart;
}
Answer provided by jewelsea is a perfect solution to this problem.
Thank you, jewelsea.
Use XYChart.Data.setNode(hoverPane) to display a custom node for each data point. Make the hoverNode a container like a StackPane. Add mouse event listeners so that you know when the mouse enters and leaves the node. On enter, place a Label for the value inside the hoverPane. On exit, remove the label from the hoverPane.
There is some example code to demonstrate this technique.
Output of the sample code is shown with the cursor hovered over the 22 node.
Using Tooltip:
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.control.Tooltip;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication250 extends Application
{
#Override
public void start(Stage stage)
{
stage.setTitle("Line Chart Sample");
//defining the axes
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
//creating the chart
final LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
lineChart.setTitle("Stock Monitoring, 2010");
//defining a series
XYChart.Series<Number, Number> series = new XYChart.Series();
series.setName("My portfolio");
//populating the series with data
Random rand = new Random();
TreeMap<Integer, Integer> data = new TreeMap();
//Create Chart data
for (int i = 0; i < 3; i++) {
data.put(rand.nextInt(51), rand.nextInt(51));
}
Set set = data.entrySet();
Iterator i = set.iterator();
while (i.hasNext()) {
Map.Entry me = (Map.Entry) i.next();
System.out.println(me.getKey() + " - " + me.getValue());
series.getData().add(new XYChart.Data(me.getKey(), me.getValue()));//Add data to series
}
lineChart.getData().add(series);
//loop through data and add tooltip
//THIS MUST BE DONE AFTER ADDING THE DATA TO THE CHART!
for (Data<Number, Number> entry : series.getData()) {
System.out.println("Entered!");
Tooltip t = new Tooltip(entry.getYValue().toString());
Tooltip.install(entry.getNode(), t);
}
Scene scene = new Scene(lineChart, 800, 600);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
I'm trying to use either a ScatterChart or LineChart to make a Gantt chart of sorts. I have a series of events, and I'd like to make a line connect the start and end point of each event. The x axis will be time and the y axis will be an incrementing index. So in other words, if event 1 is from t=1 to t=5, and event 2 is from t=3 to t=10, I would like to connect the points (1,1) and (5,1), and (3,2) and (10,2), and no others.
There's an implementation of a Gantt chart elsewhere on StackOverflow, which I started out with, but it is unusably slow when trying to plot ten thousand events.
I know that one solution would be to have a XYChart.Series for each event, so that for 1000 events, the data input for the chart would consist of 1000 XYChart.Series, each containing 2 XYChart.Data objects. But style-wise this would be impossible, as I want there to be multiple series here, with different colors.
Is there a simple solution here? I know that in the worst case I can do this with Paths, although I'm not sure how so I'll have to research this, but I'm not sure if it's the best solution.
EDIT: Below are the examples of what I'm trying to do. I want to make something look like this, but without the vertical lines; I want them disconnected.
EDIT EDIT: I modified the datapoints so that they could overlap each other, for another aspect of the requirements. I added a policy to not sort the input points for the line chart. This is not needed or a valid option in the case where it's a scatter chart, so if you're going back and forth, you'd have to comment out the line chart.setAxisSortingPolicy(LineChart.SortingPolicy.NONE);
Here's the source code. Going from one to the other is as simple as swapping LineChart with ScatterChart (and commenting out the line mentioned above):
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class DummyGanttLineChartApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Data<Number, Number>> dataList1 = FXCollections.observableArrayList();
ObservableList<Data<Number, Number>> dataList2 = FXCollections.observableArrayList();
dataList1.add(new Data<Number, Number>(1, 1));
dataList1.add(new Data<Number, Number>(2, 1));
dataList1.add(new Data<Number, Number>(1.5, 2));
dataList1.add(new Data<Number, Number>(2.5, 2));
dataList1.add(new Data<Number, Number>(2, 3));
dataList1.add(new Data<Number, Number>(3, 3));
dataList2.add(new Data<Number, Number>(1.5, 4));
dataList2.add(new Data<Number, Number>(2.5, 4));
dataList2.add(new Data<Number, Number>(2, 5));
dataList2.add(new Data<Number, Number>(3, 5));
dataList2.add(new Data<Number, Number>(2.5, 6));
dataList2.add(new Data<Number, Number>(3.5, 6));
Series<Number, Number> series1 = new Series<Number, Number>("List 1", dataList1);
Series<Number, Number> series2 = new Series<Number, Number>("List 2", dataList2);
ObservableList<Series<Number, Number>> data = FXCollections.observableArrayList();
data.add(series1);
data.add(series2);
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
xAxis.autoRangingProperty().set(false);
xAxis.setLowerBound(0);
xAxis.setUpperBound(5);
LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis, yAxis);
chart.setAxisSortingPolicy(LineChart.SortingPolicy.NONE);
chart.getData().addAll(data);
VBox box = new VBox(10);
box.getChildren().add(chart);
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
}
This is a separate class that actually launches this (a workaround for some weirdness with my setup. You should be able to use a main method inside DummyGanttLineChartApp, but I get an error). Don't worry about that.
import javafx.application.Application;
public class DummyGanttLineChart {
public static void main(String [] args) {
Application.launch(DummyGanttLineChartApp.class, args);
}
}
Thanks for providing the screenshot and data. Considering your requirement, I think customizing ScatterChart can help you to get the desired behavior.
The idea is, we figure out the min and max data points for each unique Y value and draw a line between them (I am using Path). I am picking the series color from the data points itself and applying it on the Path. This works well good when resizing the chart as well.
Note: I have not tested the performance part as you mentioned (ten thousand events), may be you can give a try and update me about the performance :)
Below is the quick sample demo of what I have tried:
import javafx.application.Application;
import javafx.beans.NamedArg;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
public class DummyGanttLineChartApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Data<Number, Number>> dataList1 = FXCollections.observableArrayList();
ObservableList<Data<Number, Number>> dataList2 = FXCollections.observableArrayList();
dataList1.add(new Data<Number, Number>(1, 1));
dataList1.add(new Data<Number, Number>(2, 1));
dataList1.add(new Data<Number, Number>(1.5, 2));
dataList1.add(new Data<Number, Number>(2.5, 2));
dataList1.add(new Data<Number, Number>(3.5, 2)); // Including this to test whether it is picking min and max points ;)
dataList1.add(new Data<Number, Number>(2, 3));
dataList1.add(new Data<Number, Number>(3, 3));
dataList2.add(new Data<Number, Number>(1.5, 4));
dataList2.add(new Data<Number, Number>(2.5, 4));
dataList2.add(new Data<Number, Number>(2, 5));
dataList2.add(new Data<Number, Number>(3, 5));
dataList2.add(new Data<Number, Number>(2.5, 6));
dataList2.add(new Data<Number, Number>(3.5, 6));
Series<Number, Number> series1 = new Series<Number, Number>("List 1", dataList1);
Series<Number, Number> series2 = new Series<Number, Number>("List 2", dataList2);
ObservableList<Series<Number, Number>> data = FXCollections.observableArrayList();
data.add(series1);
data.add(series2);
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
xAxis.autoRangingProperty().set(false);
xAxis.setLowerBound(0);
xAxis.setUpperBound(5);
GanttLineChart chart = new GanttLineChart(xAxis, yAxis);
chart.getData().addAll(data);
VBox box = new VBox(10);
box.getChildren().add(chart);
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
/**
* Custom GanttLine chart.
*/
class GanttLineChart extends ScatterChart<Number, Number> {
/**
* References to all paths. Usually one path per series.
*/
final List<Path> paths = new ArrayList<>();
public GanttLineChart(#NamedArg("xAxis") Axis<Number> xAxis, #NamedArg("yAxis") Axis<Number> yAxis) {
super(xAxis, yAxis, FXCollections.observableArrayList());
}
#Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
// Ensure to remove previous paths.
getPlotChildren().removeAll(paths);
for (XYChart.Series<Number, Number> series : getData()) {
// Map to keep references of min and max items for each unique Y value.
final Map<Number, MinMax<XYChart.Data<Number, Number>, XYChart.Data<Number, Number>>> yMap = new HashMap<>();
for (XYChart.Data<Number, Number> item : series.getData()) {
final Number y = item.getYValue();
MinMax<XYChart.Data<Number, Number>, XYChart.Data<Number, Number>> bounds = yMap.get(y);
if (bounds == null) {
bounds = new MinMax<>(item, item);
yMap.put(y, bounds);
}
if (bounds.getMin() != item) {
if (compareTo(item.getXValue(), bounds.getMin().getXValue()) < 0) {
bounds.setMin(item);
}
}
if (bounds.getMax() != item) {
if (compareTo(item.getXValue(), bounds.getMin().getXValue()) > 0) {
bounds.setMax(item);
}
}
}
final AtomicBoolean filled = new AtomicBoolean();
// Construct a path per series.
final Path path = new Path();
path.setStrokeWidth(2);
yMap.forEach((k, v) -> {
if (!filled.get()) {
Region node = (Region) v.getMin().getNode();
node.applyCss();
path.setFill(node.getBackground().getFills().get(0).getFill());
path.setStroke(path.getFill());
filled.set(true);
}
Bounds minPointBounds = v.getMin().getNode().getBoundsInParent();
Bounds maxPointBounds = v.getMax().getNode().getBoundsInParent();
double minX = minPointBounds.getMinX() + minPointBounds.getWidth() / 2;
double minY = minPointBounds.getMinY() + minPointBounds.getHeight() / 2;
double maxX = maxPointBounds.getMinX() + maxPointBounds.getWidth() / 2;
double maxY = maxPointBounds.getMinY() + maxPointBounds.getHeight() / 2;
// Draw a line in the path from min to max points for each unique Y value.
path.getElements().addAll(new MoveTo(minX, minY), new LineTo(maxX, maxY));
});
// Add the path to plot children and keep a reference for later use.
getPlotChildren().add(path);
paths.add(path);
}
}
private int compareTo(Number n1, Number n2) {
BigDecimal b1 = new BigDecimal(n1.doubleValue());
BigDecimal b2 = new BigDecimal(n2.doubleValue());
return b1.compareTo(b2);
}
/**
* Data object to hold min and max items.
*/
class MinMax<K, V> {
private K min;
private V max;
public MinMax(#NamedArg("key") K min, #NamedArg("value") V max) {
this.min = min;
this.max = max;
}
public K getMin() {
return min;
}
public V getMax() {
return max;
}
public void setMin(K min) {
this.min = min;
}
public void setMax(V max) {
this.max = max;
}
}
}
}
In JavaFX 8, I am printing a node (e.g., ScatterChart) with printerJob.printPage(). Without scaling, the printed node is cropped. If I scale for printing, then the printed node is correctly fit to the page, but the displayed node is scaled. A simple solution would be to make a copy/clone of the node, but it appears that isn't supported. Is there a better solution than scaling the node and then removing the scaling (which causes the displayed node to briefly rescale, which is unsightly)? It would seem that printing a graph would be a basic operation for JavaFX.
You can play with this app. It creates a PNG of the chart. It then prints the chart. I didn't scale the image. The actual image is located in your source folder. You can also open it using Paint and print from there. You can also code the printer settings so that the printer dialog shows up before printing.
import java.awt.Graphics;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import static java.awt.print.Printable.NO_SUCH_PAGE;
import static java.awt.print.Printable.PAGE_EXISTS;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
/**
*
* #author blj0011
*/
public class JavaFXApplication145 extends Application
{
final static String itemA = "A";
final static String itemB = "B";
final static String itemC = "F";
#Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final CategoryAxis yAxis = new CategoryAxis();
final BarChart<Number, String> bc = new BarChart<Number, String>(xAxis, yAxis);
bc.setTitle("Summary");
xAxis.setLabel("Value");
xAxis.setTickLabelRotation(90);
yAxis.setLabel("Item");
XYChart.Series series1 = new XYChart.Series();
series1.setName("2003");
series1.getData().add(new XYChart.Data(2, itemA));
series1.getData().add(new XYChart.Data(20, itemB));
series1.getData().add(new XYChart.Data(10, itemC));
XYChart.Series series2 = new XYChart.Series();
series2.setName("2004");
series2.getData().add(new XYChart.Data(50, itemA));
series2.getData().add(new XYChart.Data(41, itemB));
series2.getData().add(new XYChart.Data(45, itemC));
XYChart.Series series3 = new XYChart.Series();
series3.setName("2005");
series3.getData().add(new XYChart.Data(45, itemA));
series3.getData().add(new XYChart.Data(44, itemB));
series3.getData().add(new XYChart.Data(18, itemC));
Button button = new Button("Print Chart");
button.setOnAction((event)->{printImage(saveAsPng(bc));});//Create the image and print it.
VBox vbox = new VBox();
vbox.getChildren().add(bc);
StackPane stackPane = new StackPane();
stackPane.getChildren().add(button);
vbox.getChildren().add(stackPane);
Scene scene = new Scene(vbox, 800, 600);
bc.getData().addAll(series1, series2, series3);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
public File saveAsPng(BarChart barChart) {
WritableImage image = barChart.snapshot(new SnapshotParameters(), null);
// TODO: probably use a file chooser here
File file = new File("chart.png");
try {
ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file);
} catch (IOException e) {
// TODO: handle exception here
}
return file;
}
private void printImage(File file) {
Image image = new Image(file.toURI().toString());
java.awt.image.BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
PrinterJob printJob = PrinterJob.getPrinterJob();
printJob.setPrintable(new Printable() {
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
// Get the upper left corner that it printable
int x = (int) Math.ceil(pageFormat.getImageableX());
int y = (int) Math.ceil(pageFormat.getImageableY());
if (pageIndex != 0) {
return NO_SUCH_PAGE;
}
graphics.drawImage(bufferedImage, x, y, bufferedImage.getWidth(), bufferedImage.getHeight(), null);
return PAGE_EXISTS;
}
});
try {
printJob.print();
} catch (PrinterException e1) {
e1.printStackTrace();
}
}
}
My desktop application has a timer for starting and stopping a test. On the graph, I want to create two vertical lines to indicate the start and stop time. "Adding vertical lines to StackPane with JavaFX" won't work for my case because I don't want the lines to stay at the same position and those lines should be drawn within the plot not the layout. When the user zooms on the chart, those vertical lines should move corresponding to where the user zooms. Thanks for any tip.
Here are my codes for creating the chart:
LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis, yAxis, dataset);
xAxis.setLabel("time(s)");
yAxis.setLabel("deg/s");
You need to extend the LineChart class and override the layoutPlotChildren method in order to show your markers.
Kleopatra did a very good example for a Scatter chart. The code below is a modified version for a line chart and has both vertical and horizontal markers:
public class LineChartSample extends Application {
#Override public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
final LineChartWithMarkers<Number,Number> lineChart = new LineChartWithMarkers<Number,Number>(xAxis,yAxis);
XYChart.Series series = new XYChart.Series();
series.setName("My portfolio");
series.getData().add(new XYChart.Data(1, 23));
series.getData().add(new XYChart.Data(2, 14));
series.getData().add(new XYChart.Data(3, 15));
series.getData().add(new XYChart.Data(4, 24));
series.getData().add(new XYChart.Data(5, 34));
series.getData().add(new XYChart.Data(6, 36));
series.getData().add(new XYChart.Data(7, 22));
series.getData().add(new XYChart.Data(8, 45));
series.getData().add(new XYChart.Data(9, 43));
series.getData().add(new XYChart.Data(10, 17));
series.getData().add(new XYChart.Data(11, 29));
series.getData().add(new XYChart.Data(12, 25));
lineChart.getData().add(series);
Data<Number, Number> horizontalMarker = new Data<>(0, 25);
lineChart.addHorizontalValueMarker(horizontalMarker);
Data<Number, Number> verticalMarker = new Data<>(10, 0);
lineChart.addVerticalValueMarker(verticalMarker);
Slider horizontalMarkerSlider = new Slider(yAxis.getLowerBound(), yAxis.getUpperBound(), 0);
horizontalMarkerSlider.setOrientation(Orientation.VERTICAL);
horizontalMarkerSlider.setShowTickLabels(true);
horizontalMarkerSlider.valueProperty().bindBidirectional(horizontalMarker.YValueProperty());
horizontalMarkerSlider.minProperty().bind(yAxis.lowerBoundProperty());
horizontalMarkerSlider.maxProperty().bind(yAxis.upperBoundProperty());
Slider verticalMarkerSlider = new Slider(xAxis.getLowerBound(), xAxis.getUpperBound(), 0);
verticalMarkerSlider.setOrientation(Orientation.HORIZONTAL);
verticalMarkerSlider.setShowTickLabels(true);
verticalMarkerSlider.valueProperty().bindBidirectional(verticalMarker.XValueProperty());
verticalMarkerSlider.minProperty().bind(xAxis.lowerBoundProperty());
verticalMarkerSlider.maxProperty().bind(xAxis.upperBoundProperty());
BorderPane borderPane = new BorderPane();
borderPane.setCenter( lineChart);
borderPane.setTop(verticalMarkerSlider);
borderPane.setRight(horizontalMarkerSlider);
Scene scene = new Scene(borderPane,800,600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private class LineChartWithMarkers<X,Y> extends LineChart {
private ObservableList<Data<X, Y>> horizontalMarkers;
private ObservableList<Data<X, Y>> verticalMarkers;
public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
super(xAxis, yAxis);
horizontalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()});
horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
verticalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
verticalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}
public void addHorizontalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (horizontalMarkers.contains(marker)) return;
Line line = new Line();
marker.setNode(line );
getPlotChildren().add(line);
horizontalMarkers.add(marker);
}
public void removeHorizontalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
horizontalMarkers.remove(marker);
}
public void addVerticalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (verticalMarkers.contains(marker)) return;
Line line = new Line();
marker.setNode(line );
getPlotChildren().add(line);
verticalMarkers.add(marker);
}
public void removeVerticalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
verticalMarkers.remove(marker);
}
#Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
for (Data<X, Y> horizontalMarker : horizontalMarkers) {
Line line = (Line) horizontalMarker.getNode();
line.setStartX(0);
line.setEndX(getBoundsInLocal().getWidth());
line.setStartY(getYAxis().getDisplayPosition(horizontalMarker.getYValue()) + 0.5); // 0.5 for crispness
line.setEndY(line.getStartY());
line.toFront();
}
for (Data<X, Y> verticalMarker : verticalMarkers) {
Line line = (Line) verticalMarker.getNode();
line.setStartX(getXAxis().getDisplayPosition(verticalMarker.getXValue()) + 0.5); // 0.5 for crispness
line.setEndX(line.getStartX());
line.setStartY(0d);
line.setEndY(getBoundsInLocal().getHeight());
line.toFront();
}
}
}
}
In order to add more marker lines, just use this:
Data<Number, Number> verticalMarker = new Data<>(10, 0);
lineChart.addVerticalValueMarker(verticalMarker);
Of course you could as well use a rectangle instead of a line like this:
private ObservableList<Data<X, X>> verticalRangeMarkers;
public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
...
verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()}); // 2nd type of the range is X type as well
verticalRangeMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}
public void addVerticalRangeMarker(Data<X, X> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (verticalRangeMarkers.contains(marker)) return;
Rectangle rectangle = new Rectangle(0,0,0,0);
rectangle.setStroke(Color.TRANSPARENT);
rectangle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.2));
marker.setNode( rectangle);
getPlotChildren().add(rectangle);
verticalRangeMarkers.add(marker);
}
public void removeVerticalRangeMarker(Data<X, X> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
verticalRangeMarkers.remove(marker);
}
protected void layoutPlotChildren() {
...
for (Data<X, X> verticalRangeMarker : verticalRangeMarkers) {
Rectangle rectangle = (Rectangle) verticalRangeMarker.getNode();
rectangle.setX( getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()) + 0.5); // 0.5 for crispness
rectangle.setWidth( getXAxis().getDisplayPosition(verticalRangeMarker.getYValue()) - getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()));
rectangle.setY(0d);
rectangle.setHeight(getBoundsInLocal().getHeight());
rectangle.toBack();
}
}
used like this:
Data<Number, Number> verticalRangeMarker = new Data<>(4, 10);
lineChart.addVerticalRangeMarker(verticalRangeMarker);
To make it look like a range:
I'm not sure which question you are referring to. You can basically do all this with some binding magic: the trick is to map the x value of the line to coordinates relative to the xAxis using xAxis.getDisplayPosition(...). Then you need to transform that coordinate to the coordinate relative to the container holding the chart and the line: the easiest way to do this is to first transform to Scene coordinates using xAxis.localToScene(...) and then to the coordinates of the container, using container.sceneToLocal(...).
Then you just need to let the binding observe everything that it needs to watch for changes: these will be the (numerical) bounds of the axes, the (graphical) bounds of the chart, and, if the line is going to move, a property representing its x-value.
Here is an SSCCE. In this example, I use a Slider to move the line around. I also make the line visible only if it's in range, and bind the y-coordinates so it spans the yAxis.
import java.util.Random;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ObservableDoubleValue;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
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.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class LineChartWithVerticalLine extends Application {
#Override
public void start(Stage primaryStage) {
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
chart.getData().add(createSeries());
Pane chartHolder = new Pane();
chartHolder.getChildren().add(chart);
DoubleProperty lineX = new SimpleDoubleProperty();
Slider slider = new Slider();
slider.minProperty().bind(xAxis.lowerBoundProperty());
slider.maxProperty().bind(xAxis.upperBoundProperty());
slider.setPadding(new Insets(20));
lineX.bind(slider.valueProperty());
chartHolder.getChildren().add(createVerticalLine(chart, xAxis, yAxis, chartHolder, lineX));
BorderPane root = new BorderPane(chartHolder, null, null, slider, null);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private Line createVerticalLine(XYChart<Number, Number> chart, NumberAxis xAxis, NumberAxis yAxis, Pane container, ObservableDoubleValue x) {
Line line = new Line();
line.startXProperty().bind(Bindings.createDoubleBinding(() -> {
double xInAxis = xAxis.getDisplayPosition(x.get());
Point2D pointInScene = xAxis.localToScene(xInAxis, 0);
double xInContainer = container.sceneToLocal(pointInScene).getX();
return xInContainer ;
},
x,
chart.boundsInParentProperty(),
xAxis.lowerBoundProperty(),
xAxis.upperBoundProperty()));
line.endXProperty().bind(line.startXProperty());
line.startYProperty().bind(Bindings.createDoubleBinding(() -> {
double lowerY = yAxis.getDisplayPosition(yAxis.getLowerBound());
Point2D pointInScene = yAxis.localToScene(0, lowerY);
double yInContainer = container.sceneToLocal(pointInScene).getY();
return yInContainer ;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty()));
line.endYProperty().bind(Bindings.createDoubleBinding(() -> {
double upperY = yAxis.getDisplayPosition(yAxis.getUpperBound());
Point2D pointInScene = yAxis.localToScene(0, upperY);
double yInContainer = container.sceneToLocal(pointInScene).getY();
return yInContainer ;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty()));
line.visibleProperty().bind(
Bindings.lessThan(x, xAxis.lowerBoundProperty())
.and(Bindings.greaterThan(x, xAxis.upperBoundProperty())).not());
return line ;
}
private Series<Number, Number> createSeries() {
Series<Number, Number> series = new Series<>();
series.setName("Data");
Random rng = new Random();
for (int i=0; i<=20; i++) {
series.getData().add(new Data<>(i, rng.nextInt(101)));
}
return series ;
}
public static void main(String[] args) {
launch(args);
}
}
I was able to create a drag and zoom in feature using the Line Chart Example mentioned here. The code listens to the mouse events and adds to the vertical ranges, which makes it appear to be dragging. JavaFX Drag and Zoom Line Chart Example
/**
* The ChartView.
*/
public class ChartController {
private ChartViewModel chartViewModel;
private CustomLineChart<Number, Number> lineChart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private XYChart.Series<Number, Number> series;
private List<Integer> data;
private boolean mouseDragged;
private double initialNumberStart;
private double initialNumberEnd;
#FXML
private VBox mainContainer;
#FXML
private HBox chartContainer;
/**
* The constructor.
*/
public ChartController() {
chartViewModel = new ChartViewModel();
mouseDragged = false;
}
/**
* The initialize method.
*/
public void initialize() {
createChart();
handleEvents();
}
/**
* Handles the events.
*/
private void handleEvents() {
lineChart.setOnMousePressed(pressed -> {
int minSize = 1;
// Get coordinate from the scene and transform to coordinates from the chart axis
Point2D firstSceneCoordinate = new Point2D(pressed.getSceneX(), pressed.getSceneY());
double firstX = xAxis.sceneToLocal(firstSceneCoordinate).getX();
lineChart.setOnMouseDragged(dragged -> {
mouseDragged = true;
Point2D draggedSceneCoordinate = new Point2D(dragged.getSceneX(), dragged.getSceneY());
double draggedX = xAxis.sceneToLocal(draggedSceneCoordinate).getX();
List<Double> numbers = filterSeries(firstX, draggedX);
int size = numbers.size();
double numberStart = size > minSize ? numbers.get(0) : initialNumberStart;
double numberEnd = numbers.size() > minSize ? numbers.get(size - 1) : initialNumberEnd;
if (size > minSize) {
lineChart.addVerticalRangeLines(new Data<>(numberStart, numberEnd));
}
lineChart.setOnMouseReleased(released -> {
if (mouseDragged) {
initialNumberStart = numberStart;
initialNumberEnd = numberEnd;
mouseDragged = false;
redrawChart();
}
});
});
});
}
/**
* Creates the charts.
*/
private void createChart() {
xAxis = new NumberAxis();
yAxis = new NumberAxis();
lineChart = new CustomLineChart<>(xAxis, yAxis);
data = chartViewModel.getData();
createSeries(data);
lineChart.getData().add(series);
initialNumberStart = 1;
initialNumberEnd = data.size() - 1;
chartContainer.getChildren().add(lineChart);
HBox.setHgrow(lineChart, Priority.ALWAYS);
}
/**
* Creates the series for the line chart.
*
* #param numbers The list of numbers for the series
*/
private void createSeries(List<Integer> numbers) {
int size = numbers.size();
series = new XYChart.Series<>();
series.setName("Example");
for (int i = 0; i < size; i++) {
series.getData().add(new XYChart.Data<Number, Number>(i, numbers.get(i)));
}
}
/**
* Filters the nodes and returns the node x positions within the firstX and lastX positions.
*
* #param firstX The first x position
* #param lastX The last x position
* #return The x positions for the nodes within the firstX and lastX
*/
private List<Double> filterSeries(double firstX, double lastX) {
List<Double> nodeXPositions = new ArrayList<>();
lineChart.getData().get(0).getData().forEach(node -> {
double nodeXPosition = lineChart.getXAxis().getDisplayPosition(node.getXValue());
if (nodeXPosition >= firstX && nodeXPosition <= lastX) {
nodeXPositions.add(Double.parseDouble(node.getXValue().toString()));
}
});
return nodeXPositions;
}
/**
* Updates the series for the chart.
*/
private void updateSeries() {
lineChart.getData().remove(0);
lineChart.getData().add(series);
}
/**
* Redraws the chart.
*/
private void redrawChart() {
List<Integer> filteredSeries = new ArrayList<>();
data.forEach(number -> {
if (number >= initialNumberStart && number <= initialNumberEnd) {
filteredSeries.add(number);
}
});
if (!filteredSeries.isEmpty()) {
createSeries(filteredSeries);
updateSeries();
lineChart.removeVerticalRangeLines();
}
}
/**
* Resets the series for the chart.
*
* #param event The event
*/
#FXML
void resetChart(ActionEvent event) {
createSeries(data);
updateSeries();
}
}
Task
I'd like to create a Gantt chart with JavaFX from scratch.
Example
Let's say I have 2 machines Machine A and Machine B and they have 2 states Online (green) and Offline (red). I'd like to show their states at given time intervals in horizontal colored Bars, X-axis is the Date axis, Y-axis is the machine axis.
Question
Where do I start? Which classes do I need to use? It would be great if anyone has a minimal example and could share it.
Thank you very much.
It turned out that the BubbleChart source was a good example to start from.
Basically you can use a modified version of the XYChart and its data. What you need to do is to add extradata like how long the value is valid and some style for the coloring.
What's left is to use a date axis and hence date values instead of numeric ones.
Here's what I've come up with in case anyone wants to toy around with it:
Another example:
The source:
GanttChart.java:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javafx.beans.NamedArg;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ValueAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
public class GanttChart<X,Y> extends XYChart<X,Y> {
public static class ExtraData {
public long length;
public String styleClass;
public ExtraData(long lengthMs, String styleClass) {
super();
this.length = lengthMs;
this.styleClass = styleClass;
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public String getStyleClass() {
return styleClass;
}
public void setStyleClass(String styleClass) {
this.styleClass = styleClass;
}
}
private double blockHeight = 10;
public GanttChart(#NamedArg("xAxis") Axis<X> xAxis, #NamedArg("yAxis") Axis<Y> yAxis) {
this(xAxis, yAxis, FXCollections.<Series<X, Y>>observableArrayList());
}
public GanttChart(#NamedArg("xAxis") Axis<X> xAxis, #NamedArg("yAxis") Axis<Y> yAxis, #NamedArg("data") ObservableList<Series<X,Y>> data) {
super(xAxis, yAxis);
if (!(xAxis instanceof ValueAxis && yAxis instanceof CategoryAxis)) {
throw new IllegalArgumentException("Axis type incorrect, X and Y should both be NumberAxis");
}
setData(data);
}
private static String getStyleClass( Object obj) {
return ((ExtraData) obj).getStyleClass();
}
private static double getLength( Object obj) {
return ((ExtraData) obj).getLength();
}
#Override protected void layoutPlotChildren() {
for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex++) {
Series<X,Y> series = getData().get(seriesIndex);
Iterator<Data<X,Y>> iter = getDisplayedDataIterator(series);
while(iter.hasNext()) {
Data<X,Y> item = iter.next();
double x = getXAxis().getDisplayPosition(item.getXValue());
double y = getYAxis().getDisplayPosition(item.getYValue());
if (Double.isNaN(x) || Double.isNaN(y)) {
continue;
}
Node block = item.getNode();
Rectangle ellipse;
if (block != null) {
if (block instanceof StackPane) {
StackPane region = (StackPane)item.getNode();
if (region.getShape() == null) {
ellipse = new Rectangle( getLength( item.getExtraValue()), getBlockHeight());
} else if (region.getShape() instanceof Rectangle) {
ellipse = (Rectangle)region.getShape();
} else {
return;
}
ellipse.setWidth( getLength( item.getExtraValue()) * ((getXAxis() instanceof NumberAxis) ? Math.abs(((NumberAxis)getXAxis()).getScale()) : 1));
ellipse.setHeight(getBlockHeight() * ((getYAxis() instanceof NumberAxis) ? Math.abs(((NumberAxis)getYAxis()).getScale()) : 1));
y -= getBlockHeight() / 2.0;
// Note: workaround for RT-7689 - saw this in ProgressControlSkin
// The region doesn't update itself when the shape is mutated in place, so we
// null out and then restore the shape in order to force invalidation.
region.setShape(null);
region.setShape(ellipse);
region.setScaleShape(false);
region.setCenterShape(false);
region.setCacheShape(false);
block.setLayoutX(x);
block.setLayoutY(y);
}
}
}
}
}
public double getBlockHeight() {
return blockHeight;
}
public void setBlockHeight( double blockHeight) {
this.blockHeight = blockHeight;
}
#Override protected void dataItemAdded(Series<X,Y> series, int itemIndex, Data<X,Y> item) {
Node block = createContainer(series, getData().indexOf(series), item, itemIndex);
getPlotChildren().add(block);
}
#Override protected void dataItemRemoved(final Data<X,Y> item, final Series<X,Y> series) {
final Node block = item.getNode();
getPlotChildren().remove(block);
removeDataItemFromDisplay(series, item);
}
#Override protected void dataItemChanged(Data<X, Y> item) {
}
#Override protected void seriesAdded(Series<X,Y> series, int seriesIndex) {
for (int j=0; j<series.getData().size(); j++) {
Data<X,Y> item = series.getData().get(j);
Node container = createContainer(series, seriesIndex, item, j);
getPlotChildren().add(container);
}
}
#Override protected void seriesRemoved(final Series<X,Y> series) {
for (XYChart.Data<X,Y> d : series.getData()) {
final Node container = d.getNode();
getPlotChildren().remove(container);
}
removeSeriesFromDisplay(series);
}
private Node createContainer(Series<X, Y> series, int seriesIndex, final Data<X,Y> item, int itemIndex) {
Node container = item.getNode();
if (container == null) {
container = new StackPane();
item.setNode(container);
}
container.getStyleClass().add( getStyleClass( item.getExtraValue()));
return container;
}
#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()) {
for(Data<X,Y> data: series.getData()) {
if(xData != null) {
xData.add(data.getXValue());
xData.add(xa.toRealValue(xa.toNumericValue(data.getXValue()) + getLength(data.getExtraValue())));
}
if(yData != null){
yData.add(data.getYValue());
}
}
}
if(xData != null) xa.invalidateRange(xData);
if(yData != null) ya.invalidateRange(yData);
}
}
}
GanttChartSample.java:
import java.util.Arrays;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import chart.gantt_04.GanttChart.ExtraData;
// TODO: use date for x-axis
public class GanttChartSample extends Application {
#Override public void start(Stage stage) {
stage.setTitle("Gantt Chart Sample");
String[] machines = new String[] { "Machine 1", "Machine 2", "Machine 3" };
final NumberAxis xAxis = new NumberAxis();
final CategoryAxis yAxis = new CategoryAxis();
final GanttChart<Number,String> chart = new GanttChart<Number,String>(xAxis,yAxis);
xAxis.setLabel("");
xAxis.setTickLabelFill(Color.CHOCOLATE);
xAxis.setMinorTickCount(4);
yAxis.setLabel("");
yAxis.setTickLabelFill(Color.CHOCOLATE);
yAxis.setTickLabelGap(10);
yAxis.setCategories(FXCollections.<String>observableArrayList(Arrays.asList(machines)));
chart.setTitle("Machine Monitoring");
chart.setLegendVisible(false);
chart.setBlockHeight( 50);
String machine;
machine = machines[0];
XYChart.Series series1 = new XYChart.Series();
series1.getData().add(new XYChart.Data(0, machine, new ExtraData( 1, "status-red")));
series1.getData().add(new XYChart.Data(1, machine, new ExtraData( 1, "status-green")));
series1.getData().add(new XYChart.Data(2, machine, new ExtraData( 1, "status-red")));
series1.getData().add(new XYChart.Data(3, machine, new ExtraData( 1, "status-green")));
machine = machines[1];
XYChart.Series series2 = new XYChart.Series();
series2.getData().add(new XYChart.Data(0, machine, new ExtraData( 1, "status-green")));
series2.getData().add(new XYChart.Data(1, machine, new ExtraData( 1, "status-green")));
series2.getData().add(new XYChart.Data(2, machine, new ExtraData( 2, "status-red")));
machine = machines[2];
XYChart.Series series3 = new XYChart.Series();
series3.getData().add(new XYChart.Data(0, machine, new ExtraData( 1, "status-blue")));
series3.getData().add(new XYChart.Data(1, machine, new ExtraData( 2, "status-red")));
series3.getData().add(new XYChart.Data(3, machine, new ExtraData( 1, "status-green")));
chart.getData().addAll(series1, series2, series3);
chart.getStylesheets().add(getClass().getResource("ganttchart.css").toExternalForm());
Scene scene = new Scene(chart,620,350);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
ganttchart.css
.status-red {
-fx-background-color:rgba(128,0,0,0.7);
}
.status-green {
-fx-background-color:rgba(0,128,0,0.7);
}
.status-blue {
-fx-background-color:rgba(0,0,128,0.7);
}