Reduce number of tick labels on JavaFX xAxis - javafx

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...

Related

JavaFX LineChart Mouse Hover Values [duplicate]

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);
}
}

Is there a way to increase precision on a JavaFX chart's tick marks?

I found a problem with the decimal precision on JavaFX chart tick mark labels when changing autoranging on a chart's axis to false. Here's a quick example:
package chart;
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.chart.XYChart.Data;
import javafx.stage.Stage;
public class Chart extends Application {
#Override
public void start(Stage primaryStage) {
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
yAxis.setAutoRanging(false);
yAxis.setForceZeroInRange(false);
yAxis.setUpperBound(1);
yAxis.setLowerBound(.999);
yAxis.setTickUnit(.0001);
ScatterChart<Number, Number> chart = new ScatterChart<>(xAxis, yAxis);
chart.getData().add(new XYChart.Series<>());
Scene scene = new Scene(chart, 600, 300);
primaryStage.setScene(scene);
primaryStage.show();
chart.getData().get(0).getData().add(new Data(1, .9999));
}
public static void main(String[] args) {
launch(args);
}
}
If the autoranging is turned to true, the tick mark labels on the Y axis go to 4 decimal places while it only goes to 3 places when it is false. It also does some sort of rounding to the tick labels. How can I get the same precision on the autoranging without it being turned on?
The tickUnit property of the NumberAxis is set automatically when autoRanging is true. So by setting auto ranging, you're telling the axis to ignore your custom tick unit. See the documentation.

javafx charts not working

I am using javafx charts for my project and after running my program I am getting this chart. what I want it to snap 3rd point first and then on to 4th point. Is there any way to do that. Thanks
I'm pretty sure that the 'AreaChart' that you're using plots data monotonically. So no matter what order the points get added to your series, the chart will plot them as monotonic.
note: Monotonic means that for every x value, there is a single y value.
If you want to break the 'monotonicness' of your plot and you're willing to give up the shaded area, you could construct it as a lineChart and then you would need to change your sorting policy. I've attached an example below to show you how to fix it.
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.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Pane root = new Pane();
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
LineChart chart = new LineChart(xAxis,yAxis);
XYChart.Series series = new XYChart.Series();
chart.getData().add(series);
chart.setAxisSortingPolicy(LineChart.SortingPolicy.NONE);
series.getData().add(new XYChart.Data(0,1));
series.getData().add(new XYChart.Data(1,2));
series.getData().add(new XYChart.Data(2,1));
series.getData().add(new XYChart.Data(1,0));
root.getChildren().add(chart);
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Unfortunately, if you're looking for both, it's going to come down to having to build your own chart class by extending from XYChart or maybe even AreaChart and defining your own custom AreaChart.

JavaFX - How to position the axis label to avoid conflicting with the tick marks

The below code is a simple line chart where the yAxis tick labels are wide in terms of their character length/width. The yAxis label positioned by default is placed too close to the yAxis, meaning the text conflicts with the tick marks. Is it possible to avoid this by increasing the gap between the label and the axis?
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.stage.Stage;
public class Example extends Application {
#Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis(0, 10, 2);
final NumberAxis yAxis = new NumberAxis(1.3378, 1.3390, 0.0001);
xAxis.setLabel("xAxis");
yAxis.setLabel("yAxis");
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis){
#Override public String toString(Number object){
return String.format("%1.4f", object); }
});
final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);
XYChart.Series series = new XYChart.Series();
final XYChart.Data d1 = new XYChart.Data(0.0,1.3379);
final XYChart.Data d2 = new XYChart.Data(2.0,1.3387);
final XYChart.Data d3 = new XYChart.Data(2.5,1.3385);
final XYChart.Data d4 = new XYChart.Data(3.5,1.3387);
final XYChart.Data d5 = new XYChart.Data(8.0,1.3378);
final XYChart.Data d6 = new XYChart.Data(9.5,1.3388);
series.getData().addAll(d1, d2, d3, d4, d5, d6);
lineChart.getData().add(series);
lineChart.setLegendVisible(false);
final Scene scene = new Scene(lineChart, 400, 300);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I don't think it is possible to relocate "yAxis" label or increase the gap, but you may want to use the following workaround to avoid the text conficts:
lineChart.getYAxis().setTickLabelRotation(-90);
And you will get the following chart:
It is possible to locate the label of an axis.
For Example to move the Label 10 Pixel to the left, just use:
lineChart.getYAxis()
.lookup(".axis-label")
.setStyle("-fx-label-padding: -10 0 0 0;");

How to create BarChar that contain in each series one element (Bar) in javafx

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);
}
}

Resources