AndroidPlot background image/shader offset - androidplot

I've encountered something annoying with the background shader used as a background image: there is a width mismatch even after correcting the position offset.
A minimal project is available here: http://ge.tt/6UonbQ42/v/0
Here is a screenshot of the result:
The red line should be in sync with the background. It's OK for the first stops of the gradient but in the end, there is an offset. Is there a way to programatically know this offset or do I have to hardcode a guessed value which works for my device?
Here is the main code:
package com.example.androidplottest;
import java.util.ArrayList;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.os.Bundle;
import android.view.View;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.SimpleXYSeries;
import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYSeries;
public class MainActivity extends Activity {
public class Tuple<E> {
public E x, y;
public Tuple(E x, E y) {
this.x = x;
this.y = y;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void drawPlot(View view) {
XYPlot plot = (XYPlot) findViewById(R.id.plot);
// Test data
ArrayList<Tuple<Float>> data = new ArrayList<MainActivity.Tuple<Float>>();
data.add(new Tuple<Float>(5f, 10f));
data.add(new Tuple<Float>(5.1f, 150f));
data.add(new Tuple<Float>(5.2f, 10f));
data.add(new Tuple<Float>(5.3f, 150f));
data.add(new Tuple<Float>(5.4f, 10f));
data.add(new Tuple<Float>(9f, 150f));
data.add(new Tuple<Float>(9.1f, 10f));
data.add(new Tuple<Float>(9.2f, 150f));
data.add(new Tuple<Float>(9.3f, 10f));
data.add(new Tuple<Float>(9.4f, 150f));
data.add(new Tuple<Float>(9.5f, 10f));
data.add(new Tuple<Float>(9.6f, 150f));
data.add(new Tuple<Float>(9.7f, 10f));
data.add(new Tuple<Float>(9.8f, 150f));
data.add(new Tuple<Float>(9.9f, 10f));
data.add(new Tuple<Float>(10f, 150f));
// Create series
plot.addSeries(tupleArrayToXYSeries(data, "Data set"), new LineAndPointFormatter(Color.RED, null, null, null));
// Find the dynamic range
float max = data.get(0).y;
float min = data.get(0).y;
for(Tuple<Float> hb : data) {
if(hb.y > max) max = hb.y;
if(hb.y < min) min = hb.y;
}
// Color bounds
float minSat=0, maxSat=1;
float minVal=0f, maxVal=1;
// Target color
int[] gradientColors = new int[data.size()];
for(int i=0; i<data.size(); i++) {
float hue = 120;
float sat = Math.max(0, Math.min(1, (data.get(i).y-min)/(max-min)*(maxSat-minSat)+minSat));
float val = Math.max(0, Math.min(1, (data.get(i).y-min)/(max-min)*(maxVal-minVal)+minVal));
int color = Color.HSVToColor(new float[] {hue, sat, val});
gradientColors[i] = color;
}
// Get times
float[] timesArray = new float[data.size()];
int i = 0;
for (Tuple<Float> f : data) {
timesArray[i++] = (f != null ? (f.x-data.get(0).x)/(data.get(data.size()-1).x-data.get(0).x) : Float.NaN);
}
// Set the plot background (shader translation due to this bug:
// https://stackoverflow.com/questions/24738800/androidplot-background-image-shift)
plot.getGraphWidget().setGridPadding(0, 0, 0, 0);
plot.getGraphWidget().getGridRect();
RectF rect = plot.getGraphWidget().getGridRect();
Shader colorShader = new LinearGradient(0, 0, rect.width(), 0, gradientColors, timesArray, TileMode.CLAMP);
Matrix m = new Matrix();
m.setTranslate(rect.left, rect.top);
colorShader.setLocalMatrix(m);
plot.getGraphWidget().getGridBackgroundPaint().setColor(Color.WHITE);
plot.getGraphWidget().getGridBackgroundPaint().setShader(colorShader);
plot.redraw();
}
private XYSeries tupleArrayToXYSeries(ArrayList<Tuple<Float>> tuplesArray, String title) {
ArrayList<Float> xList = new ArrayList<Float>();
ArrayList<Float> yList = new ArrayList<Float>();
for(Tuple<Float> tuple : tuplesArray) {
xList.add(tuple.x);
yList.add(tuple.y);
}
return new SimpleXYSeries(xList, yList, title);
}
}

Related

Error:(66, 27) error: constructor LineData in class LineData cannot be applied to given types; required: ILineDataSet[] found: String[],

I was following the example on youtube, https://www.youtube.com/watch?v=TNeE9DJoOMY&t=614s, Creating a Line Graph with multiple data sets in Android Studio. I believe the code is correct... but still getting error: constuctor LineData error.. Another post on stackoverflow suggested that maybe the MPandroidChart library may have changed requiring a different input? The full error is:
Error:(66, 27) error: constructor LineData in class LineData cannot be applied to given types;
required: ILineDataSet[]
found: String[],LineDataSet
reason: varargs mismatch; String[] cannot be converted to ILineDataSet
Here is my main code:
package com.example.rj.linegraph1;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
LineChart lineChart;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lineChart = (LineChart) findViewById(R.id.lineChart);
ArrayList<String> xAXIS = new ArrayList<>();
ArrayList<Entry> yAXISsin = new ArrayList<>();
ArrayList<Entry> yAXIScos = new ArrayList<>();
double x = 0;
int numDataPoints = 1000;
for (int i = 0; i < numDataPoints; i++) {
float sinFunction = Float.parseFloat(String.valueOf(Math.sin(x)));
float cosFunction = Float.parseFloat(String.valueOf(Math.cos(x)));
x = x + 0.1;
yAXISsin.add(new Entry(sinFunction,i));
yAXIScos.add(new Entry(cosFunction,i));
xAXIS.add(i, String.valueOf(x));
}
String[] xaxis = new String[xAXIS.size()];
for(int i = 0; i < xAXIS.size(); i++) {
xaxis[i] = xAXIS.get(i).toString();
}
ArrayList<ILineDataSet> lineDataSets = new ArrayList<>();
LineDataSet lineDataSet1 = new LineDataSet(yAXIScos,"cos");
lineDataSet1.setDrawCircles(false);
lineDataSet1.setColors(Color.BLUE);
LineDataSet lineDataSet2 = new LineDataSet(yAXISsin,"sin");
lineDataSet2.setDrawCircles(false);
lineDataSet2.setColors(Color.RED);
lineDataSets.add(lineDataSet1);
lineDataSets.add(lineDataSet2);
lineChart.setData(new LineData(xaxis,lineDataSets));
lineChart.setVisibleXRangeMaximum(65f);
}
}
I think this is how the library requires you to do it now:
for (int i = 0; i < numDataPoints; i++) {
float sinFunction = Float.parseFloat(String.valueOf(Math.sin(x)));
float cosFunction = Float.parseFloat(String.valueOf(Math.cos(x)));
x = x + 0.1;
yAXISsin.add(new Entry(x, sinFunction));
yAXIScos.add(new Entry(x, cosFunction));
//dont use strings anymore
//xAXIS.add(i, String.valueOf(x));
}
ArrayList<ILineDataSet> lineDataSets = new ArrayList<>();
LineDataSet lineDataSet1 = new LineDataSet(yAXIScos,"cos");
LineDataSet lineDataSet2 = new LineDataSet(yAXISsin,"sin");
lineDataSet1.setDrawCircles(false);
lineDataSet1.setColors(Color.RED);
lineDataSet2.setDrawCircles(false);
lineDataSet2.setColors(Color.BLUE);
lineDataSets.add(lineDataSet1);
lineDataSets.add(lineDataSet2);
lineChart.setData(new LineData(lineDataSets));
lineChart.setVisibleXRangeMaximum(65f);
Pretty sure it's something like that. You no longer add the X-Axis values like before. Now the X-Axis values just get added as Entry's.
Let me know if this works. I didn't try it myself.
It might be because you need 'x' to be a double for the math operations but you need to convert it to a float for each Entry.
I also added some logs in here so you can debug it might easily.
public class MainActivity extends AppCompatActivity {
//added for debugging
private static final String TAG = "MainActivity";
LineChart lineChart;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lineChart = (LineChart) findViewById(R.id.lineChart);
ArrayList<Entry> yAXISsin = new ArrayList<>();
ArrayList<Entry> yAXIScos = new ArrayList<>();
double x = 0.0;
int numDataPoints = 1000;
for (int i = 0; i < numDataPoints; i++) {
float sinFunction = Float.parseFloat(String.valueOf(Math.sin(x)));
float cosFunction = Float.parseFloat(String.valueOf(Math.cos(x)));
x = x + 0.1;
float xEntry = Float.parseFloat(String.valueOf(x));
yAXISsin.add(new Entry(xEntry, sinFunction));
yAXIScos.add(new Entry(xEntry, cosFunction));
Log.d(TAG, "Populating Arrays with Data: yAXISsin = " + sinFunction);
Log.d(TAG, "Populating Arrays with Data: yAXIScos = " + cosFunction);
Log.d(TAG, "Populating Arrays with Data: xEntry = " + xEntry);
}
ArrayList<ILineDataSet> lineDataSets = new ArrayList<>();
LineDataSet lineDataSet1 = new LineDataSet(yAXIScos,"cos");
LineDataSet lineDataSet2 = new LineDataSet(yAXISsin,"sin");
lineDataSet1.setDrawCircles(false);
lineDataSet1.setColors(Color.RED);
lineDataSet2.setDrawCircles(false);
lineDataSet2.setColors(Color.BLUE);
lineDataSets.add(lineDataSet1);
lineDataSets.add(lineDataSet2);
lineChart.setData(new LineData(lineDataSets));
lineChart.setVisibleXRangeMaximum(65f);
//can also try calling invalidate() to refresh the graph
lineChart.invalidate();

Converting from Timeline to Thread

I am trying to create a program that runs off of a thread instead of a timeline. Here is my modified program down below. I am not sure why it will not work. Any tips would be appreciated. The thread uses a task in order to start the animation. Thanks for your help.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ch30 extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
FanPane fan = new FanPane();
HBox hBox = new HBox(5);
Button btPause = new Button("Pause");
Button btResume = new Button("Resume");
Button btReverse = new Button("Reverse");
hBox.setAlignment(Pos.CENTER);
hBox.getChildren().addAll(btPause, btResume, btReverse);
BorderPane pane = new BorderPane();
pane.setCenter(fan);
pane.setBottom(hBox);
// Create a scene and place it in the stage
Scene scene = new Scene(pane, 200, 200);
primaryStage.setTitle("Exercise15_28"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
Runnable first = new Begin();
Thread t1 = new Thread(first);
t1.start();
//Timeline animation = new Timeline(
//new KeyFrame(Duration.millis(100), e -> fan.move()));
//animation.setCycleCount(Timeline.INDEFINITE);
//animation.play(); // Start animation
scene.widthProperty().addListener(e -> fan.setW(fan.getWidth()));
scene.heightProperty().addListener(e -> fan.setH(fan.getHeight()));
//btPause.setOnAction(e -> first.wait());
btResume.setOnAction(e -> first.run());
btReverse.setOnAction(e -> fan.reverse());
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
*/
public static void main(String[] args) {
launch(args);
}
}
class FanPane extends Pane {
private double w = 200;
private double h = 200;
private double radius = Math.min(w, h) * 0.45;
private Arc arc[] = new Arc[4];
private double startAngle = 30;
private Circle circle = new Circle(w / 2, h / 2, radius);
public FanPane() {
circle.setStroke(Color.BLACK);
circle.setFill(Color.WHITE);
getChildren().add(circle);
for (int i = 0; i < 4; i++) {
arc[i] = new Arc(w / 2, h / 2, radius * 0.9, radius * 0.9, startAngle + i * 90, 35);
arc[i].setFill(Color.RED); // Set fill color
arc[i].setType(ArcType.ROUND);
getChildren().addAll(arc[i]);
}
}
private double increment = 5;
public void reverse() {
increment = -increment;
}
public void move() {
setStartAngle(startAngle + increment);
}
public void setStartAngle(double angle) {
startAngle = angle;
setValues();
}
public void setValues() {
radius = Math.min(w, h) * 0.45;
circle.setRadius(radius);
circle.setCenterX(w / 2);
circle.setCenterY(h / 2);
for (int i = 0; i < 4; i++) {
arc[i].setRadiusX(radius * 0.9);
arc[i].setRadiusY(radius * 0.9);
arc[i].setCenterX(w / 2);
arc[i].setCenterY(h / 2);
arc[i].setStartAngle(startAngle + i * 90);
}
}
public void setW(double w) {
this.w = w;
setValues();
}
public void setH(double h) {
this.h = h;
setValues();
}
}
class Begin implements Runnable {
private int times = 1000;
FanPane fan = new FanPane();
public Begin(){
// times = t;
}
#Override
public void run(){
//for (int i = 0; i < times; i++)
//{
fan.move();
}
}
Your thread needs a loop, that repeatedly pauses and then updates the UI. If you want to use an explicit thread instead of the more obvious Timeline, you need to write the code for that loop yourself.
The update of the UI cannot be called from the background thread you are creating: to update the UI on the FX Application thread you must wrap the call to the method(s) that update the UI in Platform.runLater(...).

Limit width size of Stacked Bar chart

I tried to implement this solution for StackedBar Chart but it turns out that there is no Java method getBarGap() in StackedBar chart. Is there any solution into the latest JavaFX version for this problem?
Basic example:
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainApp extends Application
{
private StackedBarChart<String, Number> stackedChart;
private List<EventsObj> eventsObj;
#Override
public void start(Stage stage) throws Exception
{
createStackedChart();
List<EventsObj> testData = generateTestData();
addStackedChartData(testData);
HBox hb = new HBox();
hb.getChildren().add(stackedChart);
Scene scene = new Scene(hb);
stage.setTitle("JavaFX and Maven");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
private void createStackedChart()
{
CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("Days");
NumberAxis yAxis = new NumberAxis();
stackedChart = new StackedBarChart<>(xAxis, yAxis);
stackedChart.setCategoryGap(20);
stackedChart.widthProperty().addListener((obs, b, b1) ->
{
// Chart Bar column is not automatically resized. We need to wait for next JavaFX releases to fix this.
Platform.runLater(() -> setMaxBarWidth(stackedChart, xAxis, 40, 10));
});
}
private List<EventsObj> generateTestData()
{
eventsObj = new ArrayList<>();
for (int i = 0; i < 5; i++)
{
eventsObj.add(new EventsObj(String.valueOf(randomDate()), random(2, 60), random(2, 60), random(2, 60), random(2, 60)));
}
return eventsObj;
}
public static int random(int lowerBound, int upperBound)
{
return (lowerBound + (int) Math.round(Math.random() * (upperBound - lowerBound)));
}
private LocalDate randomDate()
{
Random random = new Random();
int minDay = (int) LocalDate.of(1900, 1, 1).toEpochDay();
int maxDay = (int) LocalDate.of(2015, 1, 1).toEpochDay();
long randomDay = minDay + random.nextInt(maxDay - minDay);
LocalDate randomBirthDate = LocalDate.ofEpochDay(randomDay);
return randomBirthDate;
}
private void addStackedChartData(List<EventsObj> data)
{
List<XYChart.Series<String, Number>> dataSeries = new ArrayList<>(data.size());
for (EventsObj data1 : data)
{
final XYChart.Series<String, Number> series1 = new XYChart.Series<>();
series1.setName(data1.getDate());
series1.getData().setAll(
new XYChart.Data<>("Info", data1.getInfo()));
dataSeries.add(series1);
}
stackedChart.getData().setAll(dataSeries);
}
private void setMaxBarWidth(StackedBarChart<String, Number> bc, CategoryAxis xAxis, double maxBarWidth, double minCategoryGap)
{
double barWidth = 0;
do
{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (bc.getCategoryGap() + bc.getCategoryGap());
barWidth = (avilableBarSpace / bc.getData().size()) - bc.getCategoryGap();
if (barWidth > maxBarWidth)
{
avilableBarSpace = (maxBarWidth + bc.getCategoryGap()) * bc.getData().size();
bc.setCategoryGap(catSpace - avilableBarSpace - bc.getCategoryGap());
}
}
while (barWidth > maxBarWidth);
do
{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (minCategoryGap + bc.getCategoryGap());
barWidth = Math.min(maxBarWidth, (avilableBarSpace / bc.getData().size()) - bc.getCategoryGap());
avilableBarSpace = (barWidth + bc.getCategoryGap()) * bc.getData().size();
bc.setCategoryGap(catSpace - avilableBarSpace - bc.getCategoryGap());
}
while (barWidth < maxBarWidth && bc.getCategoryGap() > minCategoryGap);
}
}
In a BarChart chart, the different series added are plotted in the different columns from the same category with a barGap, some spacing in between.
But in a StackBarChart chart, the different series are plotted stacked in the same column, hence in this case there is no bar gap.
If you take the setMaxBarWidth() method from this answer, all you need to do is set bc.getBarGap() to 0, and take sbc.getData().size() as 1. In this case there is no need for iterations.
This will be the new method, really simplified:
private void setMaxCategoryWidth(double maxCategoryWidth, double minCategoryGap){
double catSpace = xAxis.getCategorySpacing();
sbc.setCategoryGap(catSpace - Math.min(maxCategoryWidth, catSpace - minCategoryGap));
}
Basically, all you have to do is subtract the desired width of your category from the initial width of the category and put this extra space into the categoryGap.
Now, you can create your chart:
StackedBarChart<String, Number> sbc = new StackedBarChart<>(xAxis, yAxis);
And set your desired size for the categories:
setMaxCategoryWidth(40, 10);
sbc.widthProperty().addListener((obs, b, b1) -> {
Platform.runLater(() -> setMaxCategoryWidth(40, 10));
});
This pic is generated based on the sample 7.4 taken from here.

BarChart Update

Now it is another problem that occurred, it seems that the query I'm using only works for the first time but after pressing the next/previous buttons, it is giving me something else !!
Here is the query I used:
for (int i = 0; i <= months.length + 1; i++) {
try {
String a;
if (i < 9) {
a = y + "0" + (i + 1);
} else {
a = y + "" + (i + 1);
}
System.out.println("Année Courante " + a);
conn = DBConnection.connect();
String sql = "select sum(montant_operation) from operations where (select Extract(YEAR_MONTH from date_operation)) = '" + a + "' and typ_operation ='Versement';";
final ResultSet rs = conn.prepareStatement(sql).executeQuery();
if (rs.next()) {
System.out.println(series1.getData().toString());
series1.getData().add(new XYChart.Data<>(months[i], rs.getFloat("sum(montant_operation)")));
}
} catch (SQLException e) {
System.out.println(e);
}
}
But is there a query that works fine one time and then it gives error.
Have a nice day
In your BuildData (by the way, not following Java Naming Conventions, change its name) method you are updating the data of series. In the same method you are adding this series to the chart. By clicking the "next" button, BuildData method is invoked where this chart is added again which is unnecessary. Delete the
Platform.runLater(() -> {
barchart.getData().add(series1);
});
part from the method and add the chart only once in start:
...
...
vbox.getChildren().addAll(box, barchart);
barchart.getData().add(series1);
pane.getChildren().add(vbox);
...
...
The tested SSCCE:
import java.util.Calendar;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
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.scene.control.Button;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class BarChartDemo extends Application {
final String[] months = {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"};
//Connection conn;
final CategoryAxis month_axis = new CategoryAxis();
final NumberAxis data_axis = new NumberAxis();
final XYChart.Series<String, Number> series1 = new XYChart.Series();
private final BarChart<String, Number> barchart = new BarChart(month_axis, data_axis);
private Integer year = 0;
#Override
public void start(Stage primaryStage) {
year = Calendar.getInstance().get(Calendar.YEAR);
Button btn_next = new Button("NEXT");
Button btn_previous = new Button("PREVIOUS");
HBox box = new HBox(50);
box.getChildren().addAll(btn_previous, btn_next);
box.setAlignment(Pos.TOP_CENTER);
VBox vbox = new VBox(25);
box.setPadding(new Insets(10, 0, 10, 0));
FlowPane pane = new FlowPane(Orientation.VERTICAL);
vbox.getChildren().addAll(box, barchart);
barchart.getData().add(series1);
pane.getChildren().add(vbox);
Scene scene = new Scene(pane);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
BuildData(year);
btn_next.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
year += 1;
BuildData(year);
}
});
btn_previous.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
year -= 1;
BuildData(year);
}
});
}
public static void main(String[] args) {
launch(args);
}
private void BuildData(Integer y) {
series1.setName("Versement");
month_axis.setLabel("Mois de l'Année");
month_axis.setStyle("-fx-font-weight:BOLD;" + "-fx-font-size:15");
data_axis.setLabel("Valeur des Opérations Bancaires");
data_axis.setStyle("-fx-font-weight:BOLD;" + "-fx-font-size:15");
series1.getData().clear(); // clear old values
for (int i = 0; i < months.length; i++) {
series1.getData().add(new XYChart.Data(months[i], i * 10 * (y-2000)));
// try {
// String a;
// if (i < 9) {
// a = y + "0" + (i + 1);
// } else {
// a = y + "" + (i + 1);
// }
// conn = DBConnection.connect();
// String sql = "select sum(montant_operation) from operations where (select Extract(YEAR_MONTH from date_operation)) = '" + a + "' and typ_operation ='Versement';";
// final ResultSet rs = conn.prepareStatement(sql).executeQuery();
// if (rs.next()) {
// series1.getData().add(new XYChart.Data<>(months[i], rs.getFloat("sum(montant_operation)")));
// }
// } catch (SQLException e) {
// System.out.println(e);
// }
}
}
}

Retrieve String values from a CategoryAxis in a javaFX Line Chart -CategoryAxis JavaFx 2.2.3 bug in .getValueForDisplay(double displayPosition)-

I have a JavaFXline chart with months versus values. I'm trying to print in the console the month and the value from a Line chart every time I click the graph .The problem is that when I call the method getValueForDisplay in my CategoryAxis object, it returns null.
When I click (x,y):jan,3
I want to print:
X value = jan Y value: 3
but the program is printing:
X value = **null** Y value: 2.7796610169491527
Another question;
2) Why when attempting to print the coordinates it seems to be not calibrated (if I click on 3 the program print 2.779661016949152? You can notice this issue checking the values printed on the console.
Here is the code I'm using:
public class LineChartTest extends Application {
String xValue;
Number yValue;
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis("Values for Y-Axis", 0, 3, 1);
yAxis.setUpperBound(5);
ObservableList<XYChart.Series<String,Double>> lineChartData = FXCollections.observableArrayList(
new LineChart.Series<String,Double>("en", FXCollections.observableArrayList(
new XYChart.Data<String,Double>("jan", 1.0),
new XYChart.Data<String,Double>("feb", 2.0),
new XYChart.Data<String,Double>("mar", 3.0),
new XYChart.Data<String,Double>("apr", 4.0),
new XYChart.Data<String,Double>("may", 0.5)
)),
new LineChart.Series<String,Double>("to", FXCollections.observableArrayList(
new XYChart.Data<String,Double>("jan", 1.6),
new XYChart.Data<String,Double>("feb", 0.4),
new XYChart.Data<String,Double>("mar", 2.9),
new XYChart.Data<String,Double>("apr", 1.3),
new XYChart.Data<String,Double>("may", 0.9)
))
);
//define a new line
final Series<String,Double> otherSeries = new Series<String,Double>();
//set points
otherSeries.getData().add(new XYChart.Data("jan",3));
otherSeries.getData().add(new XYChart.Data("feb",2));
otherSeries.getData().add(new XYChart.Data("mar",4));
//set name to the line
otherSeries.setName("tre");
//add the new line to the graph
lineChartData.add(otherSeries);
final LineChart chart = new LineChart(xAxis, yAxis, lineChartData);
//set title to the figure
chart.setTitle("Line Chart Test");
root.getChildren().add(chart);
chart.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
//Why this seems to be not working properly?
double sceneX = t.getSceneX();
double sceneY = t.getSceneY();
//gives the point of the graph
xValue = xAxis.getValueForDisplay(sceneX);
yValue = yAxis.getValueForDisplay(sceneY);
System.out.println("sceneX-25: "+ sceneX);
System.out.println("getDisplayPosition: " + xAxis.getValueForDisplay(sceneX));
//Test
System.out.println(" X value = " +
xValue + " Y value: " + yValue );
}
});
}
#Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Answer:
Thanks to Sergey Grinev and Uluk Biy
in the previous code you have to modified:
Line chart code:
chart.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
Node chartPlotBackground = chart.lookup(".chart-plot-background");
final double shiftX = xSceneShift(chartPlotBackground);
final double shiftY = ySceneShift(chartPlotBackground);
double x = e.getSceneX() - shiftX;
double y = e.getSceneY() - shiftY;
xValue = xAxis.getValueForDisplay(x);
yValue = yAxis.getValueForDisplay(y );
System.out.println("shiftX = " + shiftX + " shiftY: " + shiftY);
System.out.println("X value = "
+ xValue + " \nY value: " + yValue);
}
});
}
//recursive calls
private double xSceneShift(Node node) {
return node.getParent() == null ? 0 : node.getBoundsInParent().getMinX() + xSceneShift(node.getParent());
}
private double ySceneShift(Node node) {
return node.getParent() == null ? 0 : node.getBoundsInParent().getMinY() + ySceneShift(node.getParent());
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
in the CategoryAxis source code you have to modified (look at the comments!):
#Override public String getValueForDisplay(double displayPosition) {
if (getSide().equals(Side.TOP) || getSide().equals(Side.BOTTOM)) { // HORIZONTAL
if (displayPosition < 0 || displayPosition > getHeight()) return null; // <-------------- WRONG SHOULD BE displayPosition > getWidth()
double d = (displayPosition - firstCategoryPos.get()) / categorySpacing.get();
return toRealValue(d);
} else { // VERTICAL
if (displayPosition < 0 || displayPosition > getWidth()) return null; // <-------------- WRONG SHOULD BE displayPosition > getHeight()
double d = (displayPosition - firstCategoryPos.get()) / (categorySpacing.get() * -1);
return toRealValue(d);
}
}
you can find the source code here:
http://hg.openjdk.java.net/openjfx/2.1/master/rt/file/5c3b3d524f07/javafx-ui-controls/src/javafx/scene/chart/CategoryAxis.java
If you are aiming to display the data point that user clicked on the chart, then you can access directly to the underlining data. Hover to the data X-Y intersection points on this example:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Node;
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.scene.control.Label;
import javafx.scene.control.LabelBuilder;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
public class ChartDemo extends Application {
private DropShadow ds = new DropShadow();
#Override
public void start(Stage stage) {
stage.setTitle("Linear plot");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis(0, 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(true);
lineChart.setAlternativeRowFillVisible(false);
lineChart.setLegendVisible(false);
final 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));
final Label lbl = LabelBuilder.create().font(Font.font("Arial", FontWeight.BOLD, 25))
.textFill(Color.BLUEVIOLET).translateY(-200).build();
StackPane pane = new StackPane();
pane.getChildren().addAll(lineChart, lbl);
Scene scene = new Scene(pane, 800, 600);
lineChart.getData().addAll(series1);
for (Object obj : series1.getData()) {
final XYChart.Data data = (XYChart.Data) obj;
final Node node = data.getNode();
node.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent arg0) {
node.setEffect(ds);
node.setCursor(Cursor.HAND);
lbl.setText("X-value=" + data.getXValue().toString() + "\nY-value=" + data.getYValue().toString());
System.out.println("X-value=" + data.getXValue().toString() + ", Y-value=" + data.getYValue().toString());
}
});
node.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent arg0) {
node.setEffect(null);
node.setCursor(Cursor.DEFAULT);
lbl.setText("");
}
});
}
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
There are 2 issues.
1) You are using wrong coordinates. getValueForDisplay expects coordinates relative to chart drawing area, not to scene.
To fix that you can use next trick.
Introduce next recursive calls to calculate scene shift:
private double xSceneShift(Node node) {
return node.getParent() == null ? 0 : node.getBoundsInParent().getMinX() + xSceneShift(node.getParent());
}
private double ySceneShift(Node node) {
return node.getParent() == null ? 0 : node.getBoundsInParent().getMinY() + ySceneShift(node.getParent());
}
Find shift of chart drawing area:
Node chartPlotBackground = chart.lookup(".chart-plot-background");
final double shiftX = xSceneShift(chartPlotBackground);
final double shiftY = ySceneShift(chartPlotBackground);
And use them in your mouse handler:
public void handle(MouseEvent e) {
double x = e.getSceneX() - shiftX;
double y = e.getSceneY() - shiftY;
xValue = xAxis.getValueForDisplay(x );
yValue = yAxis.getValueForDisplay(y);
System.out.println(" X value = "
+ xValue + " \nY value: " + yValue);
}
2) You met an issue in CategoryAxis which is fixed but in next release (8.0): http://javafx-jira.kenai.com/browse/RT-25899
As a workaround you can download CategoryAxis source, rename it to MyCategoryAxis and fix a bug in method getValueForDisplay() -- just switch places of getWidth() and getHeight() call.
http://hg.openjdk.java.net/openjfx/2.1/master/rt/file/5c3b3d524f07/javafx-ui-controls/src/javafx/scene/chart/CategoryAxis.java

Resources