I am looking for some class/tool in javafx for zooming line chart.
The problem is that i am using date format on xAxis.
I have found some solutions, there are below:
https://gist.github.com/james-d/7252698
https://github.com/kerner1000/javafx-chart-zooming
https://www.programcreek.com/java-api-examples/index.php?source_dir=fancy-chart-master/src/de/tesis/dynaware/javafx/fancychart/zoom/Zoom.java
These solutions use NumberAxis and I tried integrate date format in them, but i didn't get expected result.
Here is my code:
private void setUpZooming(final Rectangle rect, final Node zoomingNode, int min, int max, int step) {
final ObjectProperty<Point2D> mouseAnchor = new SimpleObjectProperty<>();
zoomingNode.setOnMousePressed(event -> {
mouseAnchor.set(new Point2D(event.getX(), event.getY()));
rect.setWidth(0);
rect.setHeight(0);
});
zoomingNode.setOnMouseDragged(event -> {
double x = event.getX();
double y = event.getY();
rect.setX(Math.min(x, mouseAnchor.get().getX()));
rect.setY(Math.min(y, mouseAnchor.get().getY()));
rect.setWidth(Math.abs(x - mouseAnchor.get().getX()));
rect.setHeight(Math.abs(y - mouseAnchor.get().getY()));
});
zoomingNode.setOnMouseReleased(event -> {
if(rect.getHeight() >= 10 && rect.getWidth() >= 10){
doZoom(rect, (LineChart<Number, Number>) zoomingNode);
}
});
zoomingNode.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
if (e.isSecondaryButtonDown()) {
LineChart<Number, Number> lineChart = (LineChart<Number, Number>) zoomingNode;
final NumberAxis xAxis = (NumberAxis) lineChart.getXAxis();
final NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
yAxis.setLowerBound(min);
yAxis.setUpperBound(max);
rect.setWidth(0);
rect.setHeight(0);
}
});
}
private void doZoom(Rectangle zoomRect, LineChart<Number, Number> chart) {
Point2D zoomTopLeft = new Point2D(zoomRect.getX(), zoomRect.getY());
Point2D zoomBottomRight = new Point2D(zoomRect.getX() + zoomRect.getWidth(), zoomRect.getY() + zoomRect.getHeight());
final NumberAxis yAxis = (NumberAxis) chart.getYAxis();
Point2D yAxisInScene = yAxis.localToScene(0, 0);
final NumberAxis xAxis = (NumberAxis) chart.getXAxis();
Point2D xAxisInScene = xAxis.localToScene(0, 0);
yAxis.setAutoRanging(false);
xAxis.setAutoRanging(false);
double xOffset = zoomTopLeft.getX() - yAxisInScene.getX();
double yOffset = zoomBottomRight.getY() - xAxisInScene.getY();
double yAxisScale = yAxis.getScale();
double xAxisScale = xAxis.getScale();
xAxis.setLowerBound(xAxis.getLowerBound() + xOffset / xAxisScale);
xAxis.setUpperBound(xAxis.getLowerBound() + zoomRect.getWidth() / xAxisScale);
yAxis.setLowerBound(yAxis.getLowerBound() + yOffset / yAxisScale);
yAxis.setUpperBound(yAxis.getUpperBound() + zoomRect.getHeight() / yAxisScale);
zoomRect.setWidth(0);
zoomRect.setHeight(0);
}
Related
I'm trying to implement a zoom rectange for LineChart. Here is my code :
private void doZoom(Rectangle zoomRect, LineChart<Number, Number> chart) {
Point2D zoomTopLeft = new Point2D(zoomRect.getX(), zoomRect.getY());
Point2D zoomBottomRight = new Point2D(zoomRect.getX() + zoomRect.getWidth(), zoomRect.getY() + zoomRect.getHeight());
NumberAxis yAxis = (NumberAxis) chart.getYAxis();
NumberAxis xAxis = (NumberAxis) chart.getXAxis();
xAxis.setAutoRanging(false);
yAxis.setAutoRanging(false);
var xAxisMaxBoundValuePoint = xAxis.sceneToLocal(zoomBottomRight);
var xAxisMinBoundValuePoint = xAxis.sceneToLocal(zoomTopLeft);
var xAxisScaleFactor = xAxis.getScale();
xAxis.setLowerBound(xAxisMinBoundValuePoint.getX()/xAxisScaleFactor);
xAxis.setUpperBound(xAxisMaxBoundValuePoint.getX()/xAxisScaleFactor);
var yAxisMinBoundValuePoint = yAxis.sceneToLocal(zoomBottomRight);
var yAxisMaxBoundValuePoint = yAxis.sceneToLocal(zoomTopLeft);
var yAxisScaleFactor = yAxis.getScale();
yAxis.setLowerBound(yAxisMinBoundValuePoint.getY()/yAxisScaleFactor);
yAxis.setUpperBound(yAxisMaxBoundValuePoint.getY()/yAxisScaleFactor);
zoomRect.setWidth(0);
zoomRect.setHeight(0);
}
I see that xAxis limits are fine - the match zoom Rectange but yAxis are not precise. Upper bound is a little bit less and Lower bound is a little bit lower than expected. What is so special about it?
PS
I tried this https://gist.github.com/james-d/7252698 as an example but there seems to be a bug in case axis are not always positive...
Thanks to James_d, I've developed the following solution :
private void doZoom(Rectangle zoomRect, LineChart<Number, Number> chart) {
Point2D zoomTopLeft = new Point2D(zoomRect.getX(), zoomRect.getY());
Point2D zoomBottomRight = new Point2D(zoomRect.getX() + zoomRect.getWidth(),
zoomRect.getY() + zoomRect.getHeight());
var zoomTopLeftScene = zoomRect.localToScene(zoomTopLeft.getX(),zoomTopLeft.getY());
var zoomBottomRightScene = zoomRect.localToScene(zoomBottomRight.getX(), zoomBottomRight.getY());
NumberAxis yAxis = (NumberAxis) chart.getYAxis();
NumberAxis xAxis = (NumberAxis) chart.getXAxis();
xAxis.setAutoRanging(false);
yAxis.setAutoRanging(false);
var minXDisplay = xAxis.sceneToLocal(zoomTopLeftScene);
var maxXDisplay = xAxis.sceneToLocal(zoomBottomRightScene);
var xLowerBound = xAxis.getValueForDisplay(minXDisplay.getX());
var xUpperBound = xAxis.getValueForDisplay(maxXDisplay.getX());
xAxis.setLowerBound( xLowerBound.doubleValue() );
xAxis.setUpperBound( xUpperBound.doubleValue() );
var maxYDisplay = yAxis.sceneToLocal(zoomTopLeftScene);
var minYDisplay = yAxis.sceneToLocal(zoomBottomRightScene);
var yLowerBound = yAxis.getValueForDisplay(minYDisplay.getY());
var yUpperBound = yAxis.getValueForDisplay(maxYDisplay.getY());
yAxis.setLowerBound( yLowerBound.doubleValue() );
yAxis.setUpperBound( yUpperBound.doubleValue() );
zoomRect.setWidth(0);
zoomRect.setHeight(0);
}
I want to draw millimeter paper into a pdf, but when I measure the printed document I'm a bit off (drawn cm < 1 cm). I'm using the size of an A4 Paper (210 * 297) and the pageWidth and pageHeight to calculate the pixel per mm (used the average of both hoping this would work). I also tried different option when printing the document (with & without margin etc.), but this didn't work as well.
public class TestPrint extends Application {
protected static final float DINA4_IN_MM_WIDTH = 210;
protected static final float DINA4_IN_MM_HEIGHT = 297;
protected static final int LEFTSIDE = 35;
protected static final int TEXT_FONT_SIZE = 11;
protected static final int TOP_MARGIN = 60;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
File file = new File("test.pdf");
double windowWidth = 600;
double windowHeight = 1000;
float x = LEFTSIDE;
PDFont font = PDType1Font.TIMES_ROMAN;
PDPage page = new PDPage(PDRectangle.A4);
PDRectangle pageSize = page.getMediaBox();
PDDocument mainDocument = new PDDocument();
mainDocument.addPage(page);
float stringHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() * TEXT_FONT_SIZE;
float y = pageSize.getHeight() - stringHeight / 1000f - TOP_MARGIN;
float pixelPerMM = (pageSize.getWidth() / DINA4_IN_MM_WIDTH + pageSize.getHeight() / DINA4_IN_MM_HEIGHT) / 2;
float displayW = 520;
float displayH = 300;
try {
PDPageContentStream contents = new PDPageContentStream(mainDocument, page, AppendMode.APPEND, true);
drawBackgroundRaster(contents, x, y, displayW, displayH, pixelPerMM);
contents.close();
mainDocument.save(file);
ImageView imgView = getImageViewFromDocument(mainDocument, windowHeight);
VBox vBox = new VBox(imgView);
Scene scene = new Scene(vBox, windowWidth, windowHeight);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
// draw millimeter paper with dots, two boxes shall be 1cm
private void drawBackgroundRaster(PDPageContentStream contents, float x, float y, float displayW, float displayH,
float pixelPerMM) throws IOException {
// rasterColor = grey
Color rasterColor = new Color(175, 175, 175);
contents.setStrokingColor(rasterColor);
float dotSize = 0.5f;
// draw vertical lines
for (int i = 0; i <= displayW; i++) {
float xPos = x + i * pixelPerMM;
if (xPos > displayW + x) {
break;
}
contents.moveTo(xPos, y);
if (i % 5 == 0) {
contents.setLineDashPattern(new float[] {}, 0);
contents.lineTo(xPos, y - displayH);
}
contents.stroke();
}
// draw dots and horizontal lines
for (int i = 0; i <= displayH; i++) {
float yPos = y - i * pixelPerMM;
if (yPos < y - displayH) {
break;
}
contents.moveTo(x, yPos);
if (i % 5 == 0) {
contents.setLineDashPattern(new float[] {}, 0);
contents.lineTo(x + displayW, yPos);
} else {
contents.setLineDashPattern(new float[] { dotSize, pixelPerMM - dotSize }, dotSize / 2);
contents.lineTo(x + displayW, yPos);
}
contents.stroke();
}
contents.setLineDashPattern(new float[] {}, 0);
contents.moveTo(x, y);
contents.lineTo(x + displayW, y);
contents.lineTo(x + displayW, y - displayH);
contents.lineTo(x, y - displayH);
contents.lineTo(x, y);
contents.stroke();
}
private ImageView getImageViewFromDocument(PDDocument mainDocument, double windowHeight) throws IOException {
PDFRenderer pdfRenderer = new PDFRenderer(mainDocument);
BufferedImage bim = pdfRenderer.renderImageWithDPI(0, 150, ImageType.RGB);
Image image = SwingFXUtils.toFXImage(bim, null);
ImageView imageView = new ImageView(image);
double scaleFactor = windowHeight / imageView.getImage().getHeight();
double zoomFactor = scaleFactor * 2d * 2.55d / 3;
double width = imageView.getImage().getWidth() * zoomFactor;
double height = imageView.getImage().getHeight() * zoomFactor;
imageView.setFitWidth(width);
imageView.setFitHeight(height);
return imageView;
}
}
I am trying to learn how plot graph using JavaFX and there is my code where I trying to draw the very simple sin series:
public class Controller implements Initializable {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
#FXML
private LineChart<Number, Number> ExactChart = new LineChart<Number, Number>(xAxis, yAxis);
private Series sin_series = new Series();
#FXML
private void plotTheChart(ActionEvent event){
int N = 100;
double x0 = -Math.PI;
double X = Math.PI;
double h = (X-x0)/(N);
double[] x = new double[N];
double[] y = new double[N];
x[0] = x0;
for(int i = 1; i < N; i++){
x[i] = x[i-1] + h;
}
for(int i = 0; i < N; i++){
y[i] = Math.sin(x[i]);
}
sin_series.setName("sin");
for(int i = 0; i < N; i++){
sin_series.getData().add(new Data(x[i], y[i]));
}
ExactChart.getData().addAll(sin_series);
}
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
}
}
But when I execute it I face following problem:
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.String (java.lang.Double and java.lang.String are in module java.base of loader 'bootstrap')
I do not see any part of the code where I am castin double to string. Can you show me where I am wrong?
I'm trying to create vertical and horizontal lines on ScatterChart together with points. I cannot find a way to mix them up.
This is the code I generate points on chart.
vbox {
add(ScatterChart(NumberAxis(), NumberAxis()).apply {
val seriesMap: HashMap<String, XYChart.Series<Number, Number>> = HashMap()
pointsList
.map { it.decisionClass }
.distinct()
.forEach {
seriesMap.put(it, XYChart.Series())
}
for (point in pointsList) {
seriesMap.get(point.decisionClass)?.data(point.axisesValues[0], point.axisesValues[1])
}
seriesMap
.toSortedMap()
.forEach { key, value ->
value.name = key
data.add(value)
}
(xAxis as NumberAxis).setForceZeroInRange(false)
(yAxis as NumberAxis).setForceZeroInRange(false)
})
}
I don't know Kotlin, so this answer is in Java. I think you can probably translate it to Kotlin (and feel free to post another answer if so).
To add additional nodes to a chart, there are three things you need:
Call getPlotChildren() and add the new nodes to the chart's "plot children"
Override the layoutPlotChildren() method to update the positions of your nodes when the chart is laid out
Use getDisplayPosition(...), defined in Axis, to get the location in the coordinate system of the plot area of the chart from a value on the axis.
The following SSCCE creates a scatter chart somewhat similar to the one you posted in the screen shot, and adds lines gating a specified series (i.e. the line on the left extends the height of the chart and passes through the minimum x-value of the series; the line at the top extends the width of the chart, and passes through the maximum y-value of the series, etc). I added radio buttons so you can choose which series is "bounded" by the lines.
import java.util.Random;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
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.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class ScatterChartWithLines extends Application {
private final class ScatterChartWithBoundary extends ScatterChart<Number, Number> {
private Series<Number, Number> boundedSeries ;
private final NumberAxis yAxis;
private final NumberAxis xAxis;
private final Line leftLine = new Line();
private final Line rightLine = new Line();
private final Line topLine = new Line();
private final Line bottomLine = new Line();
{
getPlotChildren().addAll(leftLine, rightLine, topLine, bottomLine);
}
private ScatterChartWithBoundary(NumberAxis xAxis, NumberAxis yAxis) {
super(xAxis, yAxis);
this.yAxis = yAxis;
this.xAxis = xAxis;
}
#Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
getPlotChildren().removeAll(leftLine, rightLine, topLine, bottomLine);
if (boundedSeries != null) {
getPlotChildren().addAll(leftLine, rightLine, topLine, bottomLine);
double minX = Double.MAX_VALUE ;
double minY = Double.MAX_VALUE ;
double maxX = Double.MIN_VALUE ;
double maxY = Double.MIN_VALUE ;
for (Data<Number, Number> d : boundedSeries.getData()) {
if (d.getXValue().doubleValue() < minX) minX = d.getXValue().doubleValue() ;
if (d.getXValue().doubleValue() > maxX) maxX = d.getXValue().doubleValue() ;
if (d.getYValue().doubleValue() < minY) minY = d.getYValue().doubleValue() ;
if (d.getYValue().doubleValue() > maxY) maxY = d.getYValue().doubleValue() ;
}
positionLineInAxisCoordinates(leftLine, minX, yAxis.getLowerBound(), minX, yAxis.getUpperBound());
positionLineInAxisCoordinates(rightLine, maxX, yAxis.getLowerBound(), maxX, yAxis.getUpperBound());
positionLineInAxisCoordinates(bottomLine, xAxis.getLowerBound(), minY, xAxis.getUpperBound(), minY);
positionLineInAxisCoordinates(topLine, xAxis.getLowerBound(), maxY, xAxis.getUpperBound(), maxY);
}
}
private void positionLineInAxisCoordinates(Line line, double startX, double startY, double endX, double endY) {
double x0 = xAxis.getDisplayPosition(startX);
double x1 = xAxis.getDisplayPosition(endX);
double y0 = yAxis.getDisplayPosition(startY);
double y1 = yAxis.getDisplayPosition(endY);
line.setStartX(x0);
line.setStartY(y0);
line.setEndX(x1);
line.setEndY(y1);
}
public void setBoundedSeries(Series<Number, Number> boundedSeries) {
if (! getData().contains(boundedSeries)) {
throw new IllegalArgumentException("Specified series is not displayed in this chart");
}
this.boundedSeries = boundedSeries ;
requestChartLayout();
}
}
private final Random rng = new Random();
#Override
public void start(Stage primaryStage) {
Series<Number, Number> series1 = new Series<>("Series 1", FXCollections.observableArrayList());
Series<Number, Number> series2 = new Series<>("Series 2", FXCollections.observableArrayList());
Series<Number, Number> series3 = new Series<>("Series 3", FXCollections.observableArrayList());
for (int i = 0 ; i < 40 ; i++) {
series1.getData().add(new Data<>(rng.nextDouble()*2 + 4, rng.nextDouble()*3 + 2));
series2.getData().add(new Data<>(rng.nextDouble()*2.5 + 4.75, rng.nextDouble()*1.5 + 2));
series3.getData().add(new Data<>(rng.nextDouble()*3 + 5, rng.nextDouble()*1.5 + 2.75));
}
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
xAxis.setForceZeroInRange(false);
yAxis.setForceZeroInRange(false);
ScatterChartWithBoundary chart = new ScatterChartWithBoundary(xAxis, yAxis);
chart.getData().addAll(series1, series2, series3);
VBox buttons = new VBox(2);
ToggleGroup toggleGroup = new ToggleGroup();
for (Series<Number, Number> series : chart.getData()) {
RadioButton rb = new RadioButton(series.getName());
rb.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected) {
chart.setBoundedSeries(series);
}
});
rb.setToggleGroup(toggleGroup);
buttons.getChildren().add(rb);
}
BorderPane root = new BorderPane(chart);
root.setTop(buttons);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Here is a typical result:
Is there a way to set JavaFX Bar Chart column width size?
import javafx.application.Application;
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 {
final static String austria = "Austria";
final static String brazil = "Brazil";
final static String france = "France";
final static String italy = "Italy";
final static String usa = "USA";
#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<String,Number>(xAxis,yAxis);
bc.setTitle("Country Summary");
xAxis.setLabel("Country");
yAxis.setLabel("Value");
XYChart.Series series1 = new XYChart.Series();
series1.setName("2003");
series1.getData().add(new XYChart.Data(austria, 25601.34));
series1.getData().add(new XYChart.Data(brazil, 20148.82));
series1.getData().add(new XYChart.Data(france, 10000));
series1.getData().add(new XYChart.Data(italy, 35407.15));
series1.getData().add(new XYChart.Data(usa, 12000));
XYChart.Series series2 = new XYChart.Series();
series2.setName("2004");
series2.getData().add(new XYChart.Data(austria, 57401.85));
series2.getData().add(new XYChart.Data(brazil, 41941.19));
series2.getData().add(new XYChart.Data(france, 45263.37));
series2.getData().add(new XYChart.Data(italy, 117320.16));
series2.getData().add(new XYChart.Data(usa, 14845.27));
XYChart.Series series3 = new XYChart.Series();
series3.setName("2005");
series3.getData().add(new XYChart.Data(austria, 45000.65));
series3.getData().add(new XYChart.Data(brazil, 44835.76));
series3.getData().add(new XYChart.Data(france, 18722.18));
series3.getData().add(new XYChart.Data(italy, 17557.31));
series3.getData().add(new XYChart.Data(usa, 92633.68));
Scene scene = new Scene(bc,800,600);
bc.getData().addAll(series1, series2, series3);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For example there is a way to setBarGap size but I can't find a way to set the maximum width of a Bar Chart column.
Is there a way to solve this?
BarChart works on the basis that both barGap (gap between bars in the same category) and categoryGap (gap between bars in separate categories) can be set by the user, so for a given size of chart, the width of the bars is calculated internally every time a layout is requested.
To set a maximum width on every bar, we have to change either barGap or categoryGap accordingly. But we can do it programmatically, to respond to any change in the width of the chart.
Let's first calculate the bar width for an initial layout. According to BarChart.layoutPlotChildren():
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (bc.getCategoryGap() + bc.getBarGap());
double barWidth = (avilableBarSpace / bc.getData().size()) - bc.getBarGap();
In the example provided by the OP, given the default gap values (barGap=4, categoryGap=10), the resulting barWidth is 37.4.
Let's assume we want to set a limit of 40, and a minimum category gap of 10:
double maxBarWidth=40;
double minCategoryGap=10;
If the chart's width increases when the scene is resized, we could limit barWidth by increasing categoryGap:
double barWidth=0;
do{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (bc.getCategoryGap() + bc.getBarGap());
barWidth = (avilableBarSpace / bc.getData().size()) - bc.getBarGap();
if(barWidth > maxBarWidth){
avilableBarSpace=(maxBarWidth + bc.getBarGap())* bc.getData().size();
bc.setCategoryGap(catSpace-avilableBarSpace-bc.getBarGap());
}
} while(barWidth>maxBarWidth);
Note the do-while loop: as we modify categoryGap a new layout is performed and some initial values change. It takes usually two iterations to get the desired result.
We can repeat this operation as long as the chart's width keeps growing.
But if the chart's width starts decreasing, the gap between categories should be decreased too, while the bar width stays close to its maximum:
double barWidth=0;
do{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (minCategoryGap + bc.getBarGap());
barWidth = Math.min(maxBarWidth, (avilableBarSpace / bc.getData().size()) - bc.getBarGap());
avilableBarSpace=(barWidth + bc.getBarGap())* bc.getData().size();
bc.setCategoryGap(catSpace-avilableBarSpace-bc.getBarGap());
} while(barWidth < maxBarWidth && bc.getCategoryGap()>minCategoryGap);
Putting everything together, and listening to scene width changes:
...
Scene scene = new Scene(bc,800,600);
bc.getData().addAll(series1, series2, series3);
stage.setScene(scene);
stage.show();
double maxBarWidth=40;
double minCategoryGap=10;
scene.widthProperty().addListener((obs,n,n1)->{
if(bc.getData().size()==0) return;
if(n!=null && (n1.doubleValue()>n.doubleValue())){
double barWidth=0;
do{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (bc.getCategoryGap() + bc.getBarGap());
barWidth = (avilableBarSpace / bc.getData().size()) - bc.getBarGap();
if (barWidth >maxBarWidth){
avilableBarSpace=(maxBarWidth + bc.getBarGap())* bc.getData().size();
bc.setCategoryGap(catSpace-avilableBarSpace-bc.getBarGap());
}
} while(barWidth>maxBarWidth);
}
if(n!=null && (n1.doubleValue()<n.doubleValue()) && bc.getCategoryGap()>minCategoryGap){
double barWidth=0;
do{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (minCategoryGap + bc.getBarGap());
barWidth = Math.min(maxBarWidth, (avilableBarSpace / bc.getData().size()) - bc.getBarGap());
avilableBarSpace=(barWidth + bc.getBarGap())* bc.getData().size();
bc.setCategoryGap(catSpace-avilableBarSpace-bc.getBarGap());
} while(barWidth < maxBarWidth && bc.getCategoryGap()>minCategoryGap);
}
});
Note that in the rest of the cases, barWidth will be calculated internally.
Further to José Pereda's answer. You can set the width of the chart according to your data set. Together with the BarGap and CategoryGap setters, you can fine tune the width of the bars through calculation or trial and error. For instance, I did the following to get thinner bars. I set the width of an empty graph to 30 (for the axis and label??) and then added 100px to max and min width for every round in the game of cribbage (up to a maximum of the gridpane width) thus restricting the free space in the graph for the bars to fill.
Pane barchart1 = new Pane;
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis(0,25,1);
BarChart<String, Number> bc1 =
new BarChart<String, Number>(xAxis, yAxis);
// create Player chart
bc1.setTitle(G.getPlayerName(0));
bc1.setTitle(null);
xAxis1.setLabel("Round");
yAxis1.setLabel("Points");
XYChart.Series series1 = new XYChart.Series();
series1.setName("Pegging");
XYChart.Series series3 = new XYChart.Series();
series3.setName("Hand");
XYChart.Series series5 = new XYChart.Series();
series5.setName("Box");
for (int i = 0; i < G.getRoundsSize(); i++) {
series1.getData().add(new XYChart.Data(Integer.toString(i + 1), G.getRound(i).getPlayerRoundData(0).getPegPoints()));
series3.getData().add(new XYChart.Data(Integer.toString(i + 1), G.getRound(i).getPlayerRoundData(0).getHandPoints()));
series5.getData().add(new XYChart.Data(Integer.toString(i + 1), G.getRound(i).getPlayerRoundData(0).getBoxPoints()));
}
bc1.getData(). addAll(series1, series3, series5);
bc1.setBarGap(0);
bc1.setCategoryGap(1.0);
bc1.setMaxHeight(180);
bc1.setMinHeight(180);
if(G.getRoundsSize() < 8){
int wid = ((G.getRoundsSize() * 100) + 30);
bc1.setMaxWidth(wid);
bc1.setMinWidth(wid);
} else {
bc1.setMaxWidth(830);
bc1.setMinWidth(830);
}
barchart1.getChildren().add(bc1);
Unfortunately you cannot simply set the bar width in the Javafx BarChart. You can only set the distance between the categories CategoryGap and the bars of a category BarGap.
Since the number of categories (categoryAxis.getCategories().size()), bars per category (barChart.getData().size()) and the available space for the axis (categoryAxis.getLayoutBounds().getWidth()) are known, the CategoryGap and BarGap can be simply calculated.
Important: This needs to be recalculated, if the chart is resized.
public static void setBarWidth(BarChart<?, ?> barChart, CategoryAxis categoryAxis,
double barWidth, double availableSpace) {
int dataSeriesCount = barChart.getData().size();
int categoriesCount = categoryAxis.getCategories().size();
if (dataSeriesCount <= 1) {
barChart.setBarGap(0d);
} else {
barChart.setBarGap(5d);
}
double barWidthSum = barWidth * (categoriesCount * dataSeriesCount);
double barGapSum = barChart.getBarGap() * (dataSeriesCount - 1);
double categorySpacing = (availableSpace - barWidthSum - barGapSum) / categoriesCount;
barChart.setCategoryGap(categorySpacing);
}