Log scale for Y axis in XYChart
Hi all,
I would like to set Y axis as log scale, but it seems JavaFX does not have such possibility: searching on the web I have found suggestions about how to do to set as log scale but the cose is written in a completely different way, I never saw before.
So I am asking if someone can help me to set Y scale as log, below are my simple class to plot data in linear mode
Code:
public class BaseXYChart extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("Linear plot");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis(1, 22, 0.5);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis){
@Override
public String toString(Number object){
return String.format("%7.2f", object);
}
});
final LineChart<String, Number>lineChart = new LineChart<String, Number>(xAxis, yAxis);
lineChart.setCreateSymbols(false);
lineChart.setAlternativeRowFillVisible(false);
XYChart.Series series1 = new XYChart.Series();
series1.getData().add(new XYChart.Data("Jan", 1));
series1.getData().add(new XYChart.Data("Feb", 1.5));
series1.getData().add(new XYChart.Data("Mar", 2));
series1.getData().add(new XYChart.Data("Apr", 2.5));
series1.getData().add(new XYChart.Data("May", 3));
series1.getData().add(new XYChart.Data("Jun", 4));
series1.getData().add(new XYChart.Data("Jul", 6));
series1.getData().add(new XYChart.Data("Aug", 9));
series1.getData().add(new XYChart.Data("Sep", 12));
series1.getData().add(new XYChart.Data("Oct", 15));
series1.getData().add(new XYChart.Data("Nov", 20));
series1.getData().add(new XYChart.Data("Dec", 22));
BorderPane pane = new BorderPane();
pane.setCenter(lineChart);
Scene scene = new Scene(pane, 800, 600);
lineChart.getData().addAll(series1);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and this is what has been proposed here JavaFX chart API and logarithmic scale - dooApp technical blog
JavaFX provides an impressive chart API that allow to draw a wide range of charts. Unfortunately the only scale that is available for XY charts is a linear implementation. Since the javadoc of the API is sometimes a bit rough it took me some times to get figure out how to get a logarithmic scale. After a discussion on mix.oracle.com it turned out that there where to approaches:
the first one is to transform your data with the logarithmic function before injecting it into the chart and to adjust the formatting of the labels to "simulate" a logarithmic scale. Since I don't really like the idea of manipulating the data to get the expected representation I prefer the second approach:
the second one is to provide your own implementation of a logarithmic Axis. Let's see how to do this:
Extend the ValueAxis class. There are two methods to implement:
public getDisplayPosition(value: java.lang.Object) : Number
will do the mapping between a data value and its visual position. For a log axis, the implementation will look like this:
Code:
var logUpperBound: Number = bind Math.log10(upperBound);
var logLowerBound: Number = bind Math.log10(lowerBound);
override public function getDisplayPosition(arg0: Object): Number {
def delta = logUpperBound - logLowerBound;
def deltaV = Math.log10((arg0 as Number)) - logLowerBound;
if (orientationVertical) {
return (1 -((deltaV) / delta)) * height;
} else {
return ((deltaV) / delta) * width;
}
}
2) protected abstract updateTickMarks() : Void
will define which tick marks should be displayed. The javadoc does not say much about this function. It looks like the main idea is to update the tickMarks sequence of TickMark. The fields of TickMark are weirdly flagged as public-read package, so in oder to instantiate TickMark for our implementation we first need to define a class extending TickMark, let's say CustomTickMark, and to put it in the package javafx.scene.chart.part in order to be able to set the required fields. After that you just need to update the sequence of tick marks with the marks you want to see on your chart, which can look like this:
Code:
override protected function updateTickMarks(): Void {
if (tickMarks.size() == 0) {
for (i in [0..Math.log10(upperBound)-1], j in [1..9]) {
var v = j * Math.pow(10, i);
insert CustomTickMark {
customLabel: "{%6.0f v}"
customPosition: getDisplayPosition(v)
customValue: v
} into tickMarks;
}
}
}
Well I have never saw methods written in such way updateTickMarks(): Void , it seems a for each statment! And var, where does this come from? I know of int, double, float, String, etc. but never saw var as variable declaration.
I do not know how to set my Y scale using such methods, can someone drop me some lines of code to add log scale to my code?
Thanks!
Susie
Re: Log scale for Y axis in XYChart
Quote:
Originally Posted by
susieferrari
Well I have never saw methods written in such way updateTickMarks(): Void , it seems a for each statment! And var, where does this come from? I know of int, double, float, String, etc. but never saw var as variable declaration.
That's JavaFX Script, aka JavaFX 1.x (the last version was I think 1.3.1).
Take a bash at converting that to Java code. If I'm reading the API correctly, you will want to override Axis#calculateTickValues(double length, Object range) to return an appropriately populated java.util.List<Axis.TickMark<T>>.
db
Re: Log scale for Y axis in XYChart
Thanks Darryl,
I do not know anything about JavaFX Script: are the method posted of any help in FX 2.0?
I have no idea how to use or converting that methods, can you help me?
Thanks
Susie
Re: Log scale for Y axis in XYChart
Quote:
Originally Posted by
susieferrari
I do not know anything about JavaFX Script
Search the net; there are tutorials and guides. It's not all that difficult to learn to read the script. You don't need to go in to how to write it.
I don't know FX Script either, but the program statements are more or less self-explanatory.
Quote:
Originally Posted by
susieferrari
I have no idea how to use or converting that methods, can you help me?
Here's an approximation, typed in Notepad -- may have typos or be otherwise uncompilable when plugged into a custom Axis subclass, and will need a translation of the getDisplayPosition method. Code:
// instance field: first check whether Axis already has a protected field you can use.
private List<Axis.TickMark> tickMarks = new ArrayList<Axis.TickMark>();
// override protected function updateTickMarks(): Void {
@Override
protected java.util.List<T> calculateTickValues(double length, range) {
if (tickMarks.size() == 0) {
// for (i in [0..Math.log10(upperBound)-1], j in [1..9]) {
for (int i=0; i < Math.log10(upperBound) - 1; i++) {
for (int j = 1; j <=9; j++) {
// var v = j * Math.pow(10, i);
double v = j * Math.pow(10, i);
// insert CustomTickMark {
TickMark tickMark = new TickMark();
// customLabel: "{%6.0f v}"
tickMark.setLabel(String.format("%6.0f", v));
// customPosition: getDisplayPosition(v)
tickMark.setPosition(getDisplayPosition(v));
// customValue: v
tickMark.setValue(v);
// } into tickMarks;
tickMarks.add(tickMark);
}
}
}
}
db
Re: Log scale for Y axis in XYChart
Thank you very much, your help always greatly appreciated!
Susie
Re: Log scale for Y axis in XYChart
Hi Susie,
You could find my article on how to do it here:
Logarithmic scale strikes back in JavaFX 2.0 - dooApp technical blog
It should help you ;-)
Good luck
Kévin
Re: Log scale for Y axis in XYChart
Hi Kevin,
thank you very much for the link you've pointed out: this is the perfect answer to my question.
Great work, it's perfect!
susie