Reputation: 624
Using JavaFX Charts, I need to invert the y-axis of a stacked area chart so that a positive zero is at the top and the positive numbers work downward on the y-axis. Below is a mock-up of what I'm trying to achieve.
What is the best (read: shortest development time and high code-reuse) way to achieve this in JavaFX?
UPDATE
Converting the data to negative numbers is not an option. I'm looking for answers that will work with the positive numbers, "untouched."
Upvotes: 7
Views: 3689
Reputation: 795
Slight update on @harshtuna 's answer to avoid the reflection part. The following code worked for me using OpenJFX 16
@Override
protected void layoutChildren() {
final Side side = getSide();
boolean isHorizontal = null == side || side.isHorizontal();
double offSetting = isHorizontal ? getWidth() : getHeight();
super.layoutChildren();
if (inversed) {
double prevEnd = isHorizontal ? offSetting + getTickLabelGap() : 0;
for (TickMark m : getTickMarks()) {
double position = m.getPosition();
try {
if (0 <= position && position <= offSetting)
if (isHorizontal) {
m.setTextVisible(position < prevEnd);
prevEnd = position - (2 + getTickLabelGap());
} else {
m.setTextVisible(position > prevEnd);
prevEnd = position + (2 + getTickLabelGap());
}
} catch (Exception ignored) {
System.out.println("illegal...?");
}
}
}
}
Upvotes: 0
Reputation: 41
Use the properties lowerBound and upperBound of the axis. The lowerBound must be higher than upperBound. Here's how you can do using fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<HBox
prefHeight="600.0"
prefWidth="800.0"
xmlns:fx="http://javafx.com/fxml/1">
<javafx.scene.chart.LineChart
fx:id="chart" >
<xAxis >
<javafx.scene.chart.NumberAxis
label="X Axis"
lowerBound="0.01"
upperBound="100"
tickUnit="10"
autoRanging="false" />
</xAxis>
<yAxis>
<javafx.scene.chart.NumberAxis
label="Y Axis"
lowerBound="100"
upperBound="0.001"
tickUnit="10"
autoRanging="false"
/>
</yAxis>
</javafx.scene.chart.LineChart>
</Hbox>
Upvotes: -1
Reputation: 757
Not easy option is to enable inverse()
operation on axes. Without patching JRE classes it is quite complicated.
Some hints on how to approach this:
1) extend ValueAxis or Axis (NumberAxis is final unfortunately)
2) add boolean field and inverse()
method to your axis class
public void inverse() {
inversed = !inversed; // boolean property
invalidateRange();
requestAxisLayout();
}
3) if you extend ValueAxis - you will need to compensate the offset applied by the superclass (and intercept the code where the size of the axis is changing)
@Override
public Long getValueForDisplay(double displayPosition) {
if (inversed)
return super.getValueForDisplay(offset - displayPosition);
else
return super.getValueForDisplay(displayPosition);
}
@Override
public double getDisplayPosition(Long value) {
if (inversed)
return offset - super.getDisplayPosition(value);
else
return super.getDisplayPosition(value);
}
4) (the ugliest piece) un-hide label ticks suppressed by Axis class - the original implementation depends on default order of ticks. I did not find any other way other that unlock it via reflection. So this is very brittle.
@Override
protected void layoutChildren() {
final Side side = getSide();
boolean isHorisontal = null == side || side.isHorizontal();
this.offset = isHorisontal ? getWidth() : getHeight();
super.layoutChildren();
if (inversed) {
double prevEnd = isHorisontal ? offset + getTickLabelGap() : 0;
for (TickMark m : getTickMarks()) {
double position = m.getPosition();
try {
final Text textNode = (Text) textNodeField.get(m);
final Bounds bounds = textNode.getLayoutBounds();
if (0 <= position && position <= offset)
if (isHorisontal) {
textNode.setVisible(position < prevEnd);
prevEnd = position - (bounds.getWidth() + getTickLabelGap());
} else {
textNode.setVisible(position > prevEnd);
prevEnd = position + (bounds.getHeight() + getTickLabelGap());
}
} catch (IllegalAccessException ignored) {
}
}
}
}
Y Axis is inverted in this example
Upvotes: 1
Reputation: 34508
You can use regular axis with negative values, but add TickLabelFormatter
which will strip minus sign.
final NumberAxis yAxis = new NumberAxis(-25, 0, 5);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
@Override
public String toString(Number value) {
// note we are printing minus value
return String.format("%7.1f", -value.doubleValue());
}
});
series1.getData().add(new XYChart.Data("Jan", -1));
series1.getData().add(new XYChart.Data("Feb", -5));
series1.getData().add(new XYChart.Data("Mar", -20));
Upvotes: 3