LineChart FX - 删除实线
LineChart FX - Delete solid line
我对图表 LineChart JavaFX 有一个奇怪的问题。
我有这张图:
在 X 轴上形成 "jump" 的点(如我得分的两个红点所示),因此 JavaFX 在这两点之间绘制了一条线。如何删除每个 "jump"?
之间的那条线
我post代码:
public class ControllerIndividua {
public static void plotIndividuaFull(String path, Stage stage, String name) {
final NumberAxis xAxisIntensity = new NumberAxis(); //Dichiarazione asseX
final NumberAxis yAxisIntensity = new NumberAxis();//Dichiarazione asseY
DetectionS1.countS1();
//Dichiarazione del tipo di grafico
final LineChart<Number, Number> lineChartIntensity = new LineChart<Number, Number>(xAxisIntensity,yAxisIntensity);
ArrayList<Double> extractedData; //Lista dei valori dello dell' intensità
ArrayList<Double> extractedTime; //Lista del tempo
ArrayList<Double> extractedS1; //Lista del tempo
ArrayList<Double> extractedS1Time; //Lista del tempo
//Gestione e settaggio del grafico
lineChartIntensity.getData().clear();
try {
//Popolamento delle liste
extractedTime = IntensityExtractor.pointsTime();
extractedData = IntensityExtractor.pointsIntensity();
extractedS1 = DetectionS1.S1pitch();
extractedS1Time = DetectionS1.pointsS1Time();
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
XYChart.Series<Number, Number> seriesS1 = new XYChart.Series<Number, Number>(); //Creazione seconda serie
series.setName("Intensità di:\t" + name.toUpperCase());
for (int j = 0; j < extractedS1.size(); j++) {
seriesS1.getData().add(new XYChart.Data<Number, Number>(extractedS1Time.get(j), extractedS1.get(j)));
lineChartIntensity.getStyleClass().add("CSSintensity");
}
//Creazione finestra e stampa del grafico
Scene scene = new Scene(lineChartIntensity, 1000, 600);
lineChartIntensity.getData().addAll(series,seriesS1);
scene.getStylesheets().add("application/application.css");
stage.setScene(scene);
stage.show();
} catch (java.lang.Exception e) {
e.printStackTrace();
}
}
}
有人也知道我该怎么做吗?
提前谢谢大家。
我查过了。不可能只更改此行的一部分。因为这是一条很长的 Path,您不能更改 Path 的一个元素。
我认为唯一的方法是将每个 "jump" 添加到不同的系列中。
这是一个有公认答案的老问题,但我遇到了它并且很好奇。我想知道是否可以在 LineChart
中放置一个缺口(至少无需创建自定义图表实现)。原来是有的。该解决方案有点老套和脆弱。它涉及获取 Path
by using XYChart.Series.getNode()
and manipulating the list of PathElement
s。下面的代码给出了一个例子:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
LineChart<Number, Number> chart = new LineChart<>(new NumberAxis(), new NumberAxis());
chart.getXAxis().setLabel("X");
chart.getYAxis().setLabel("Y");
chart.setLegendVisible(false);
chart.getData().add(new XYChart.Series<>());
for (int x = 0; x <= 10; x++) {
chart.getData().get(0).getData().add(new XYChart.Data<>(x, Math.pow(x, 2)));
}
/*
* Had to wrap the call in a Platform.runLater otherwise the Path was
* redrawn after the modifications are made.
*/
primaryStage.setOnShown(we -> Platform.runLater(() -> {
Path path = (Path) chart.getData().get(0).getNode();
LineTo lineTo = (LineTo) path.getElements().get(8);
path.getElements().set(8, new MoveTo(lineTo.getX(), lineTo.getY()));
}));
primaryStage.setScene(new Scene(new StackPane(chart), 500, 300));
primaryStage.setTitle("LineChart Gap");
primaryStage.show();
}
}
此代码产生以下结果:
这是可能的,因为 PathElement
的 ObservableList
似乎是 MoveTo
followed by a bunch of LineTo
。我只是选择了一个 LineTo
并将其替换为 MoveTo
到相同的坐标。然而,我还没有完全弄清楚 LineTo
的哪个索引与 XYChart.Data
匹配,并随机选择了 8
作为示例。
这个解决方案有几个问题。第一个也是显而易见的是,这依赖于 LineChart
的内部实现。第二个是真正脆弱性的来源。对数据的任何更改,无论是轴的值范围、图表的宽度或高度,还是几乎 任何导致图表重绘的东西 都会导致重新计算 Path
并重新绘制。这意味着如果您使用此解决方案,则每次 图表重新绘制时都必须重新应用修改。
我制作了一个 GapLineChart,当 Slaw 看到 Double.NaN 时,它会自动设置一个 MoveTo,就像 Slaw 指向它一样。你可以在这里找到它 https://gist.github.com/sirolf2009/ae8a7897b57dcf902b4ed747b05641f9。查看第一条评论以获取示例
我需要调整 LineChart 以绘制线段,因此每对数据点创建一个线段。通过扩展 LineChart
并覆盖 layoutPlotChildren
函数非常简单,尽管我不得不删除一个动画 y
-scaling 变量,因为它在 super 中是 private
class,但我没有为图表制作动画,所以没问题。无论如何,这里是 Kotlin 版本(您可以轻松适应 Java,或者只需复制 Java LineChart
layoutPlotChildren
代码并适应)。很容易适应您的情况,只需在最后更改 for
循环的内容即可。
import javafx.scene.chart.Axis
import javafx.scene.chart.LineChart
import javafx.scene.shape.LineTo
import javafx.scene.shape.MoveTo
import javafx.scene.shape.Path
import java.util.*
import kotlin.collections.ArrayList
class GapLineChart<X, Y>(xAxis: Axis<X>?, yAxis: Axis<Y>?) : LineChart<X, Y>(xAxis, yAxis) {
public override fun layoutPlotChildren() {
val constructedPath = ArrayList<LineTo>(data.size)
for (seriesIndex in 0 until data.size) {
val series = data[seriesIndex]
if (series.node is Path) {
val seriesLine = (series.node as Path).elements
val it = getDisplayedDataIterator(series)
seriesLine.clear()
constructedPath.clear()
var parity = true
while (it.hasNext()) {
val item = it.next()
val x = xAxis.getDisplayPosition(item.xValue)
val y = yAxis.getDisplayPosition(yAxis.toRealValue(yAxis.toNumericValue(item.yValue)))
if (java.lang.Double.isNaN(x) || java.lang.Double.isNaN(y)) {
continue
}
if(parity)
constructedPath.add(LineTo(x, y))
val symbol = item.node
if (symbol != null) {
val w = symbol.prefWidth(-1.0)
val h = symbol.prefHeight(-1.0)
symbol.resizeRelocate(x - w / 2, y - h / 2, w, h)
}
}
when (axisSortingPolicy) {
SortingPolicy.X_AXIS -> constructedPath.sortWith(Comparator { e1, e2 -> java.lang.Double.compare(e1.x, e2.x) })
SortingPolicy.Y_AXIS -> constructedPath.sortWith(Comparator { e1, e2 -> java.lang.Double.compare(e1.y, e2.y) })
}
if (!constructedPath.isEmpty()) {
for (i in 0 until constructedPath.size-1 step 2) {
seriesLine.add(MoveTo(constructedPath[i].x, constructedPath[i].y))
seriesLine.add(LineTo(constructedPath[i+1].x, constructedPath[i+1].y))
}
}
}
}
}
}
我对图表 LineChart JavaFX 有一个奇怪的问题。
我有这张图:
我post代码:
public class ControllerIndividua {
public static void plotIndividuaFull(String path, Stage stage, String name) {
final NumberAxis xAxisIntensity = new NumberAxis(); //Dichiarazione asseX
final NumberAxis yAxisIntensity = new NumberAxis();//Dichiarazione asseY
DetectionS1.countS1();
//Dichiarazione del tipo di grafico
final LineChart<Number, Number> lineChartIntensity = new LineChart<Number, Number>(xAxisIntensity,yAxisIntensity);
ArrayList<Double> extractedData; //Lista dei valori dello dell' intensità
ArrayList<Double> extractedTime; //Lista del tempo
ArrayList<Double> extractedS1; //Lista del tempo
ArrayList<Double> extractedS1Time; //Lista del tempo
//Gestione e settaggio del grafico
lineChartIntensity.getData().clear();
try {
//Popolamento delle liste
extractedTime = IntensityExtractor.pointsTime();
extractedData = IntensityExtractor.pointsIntensity();
extractedS1 = DetectionS1.S1pitch();
extractedS1Time = DetectionS1.pointsS1Time();
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
XYChart.Series<Number, Number> seriesS1 = new XYChart.Series<Number, Number>(); //Creazione seconda serie
series.setName("Intensità di:\t" + name.toUpperCase());
for (int j = 0; j < extractedS1.size(); j++) {
seriesS1.getData().add(new XYChart.Data<Number, Number>(extractedS1Time.get(j), extractedS1.get(j)));
lineChartIntensity.getStyleClass().add("CSSintensity");
}
//Creazione finestra e stampa del grafico
Scene scene = new Scene(lineChartIntensity, 1000, 600);
lineChartIntensity.getData().addAll(series,seriesS1);
scene.getStylesheets().add("application/application.css");
stage.setScene(scene);
stage.show();
} catch (java.lang.Exception e) {
e.printStackTrace();
}
}
}
有人也知道我该怎么做吗?
提前谢谢大家。
我查过了。不可能只更改此行的一部分。因为这是一条很长的 Path,您不能更改 Path 的一个元素。
我认为唯一的方法是将每个 "jump" 添加到不同的系列中。
这是一个有公认答案的老问题,但我遇到了它并且很好奇。我想知道是否可以在 LineChart
中放置一个缺口(至少无需创建自定义图表实现)。原来是有的。该解决方案有点老套和脆弱。它涉及获取 Path
by using XYChart.Series.getNode()
and manipulating the list of PathElement
s。下面的代码给出了一个例子:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
LineChart<Number, Number> chart = new LineChart<>(new NumberAxis(), new NumberAxis());
chart.getXAxis().setLabel("X");
chart.getYAxis().setLabel("Y");
chart.setLegendVisible(false);
chart.getData().add(new XYChart.Series<>());
for (int x = 0; x <= 10; x++) {
chart.getData().get(0).getData().add(new XYChart.Data<>(x, Math.pow(x, 2)));
}
/*
* Had to wrap the call in a Platform.runLater otherwise the Path was
* redrawn after the modifications are made.
*/
primaryStage.setOnShown(we -> Platform.runLater(() -> {
Path path = (Path) chart.getData().get(0).getNode();
LineTo lineTo = (LineTo) path.getElements().get(8);
path.getElements().set(8, new MoveTo(lineTo.getX(), lineTo.getY()));
}));
primaryStage.setScene(new Scene(new StackPane(chart), 500, 300));
primaryStage.setTitle("LineChart Gap");
primaryStage.show();
}
}
此代码产生以下结果:
这是可能的,因为 PathElement
的 ObservableList
似乎是 MoveTo
followed by a bunch of LineTo
。我只是选择了一个 LineTo
并将其替换为 MoveTo
到相同的坐标。然而,我还没有完全弄清楚 LineTo
的哪个索引与 XYChart.Data
匹配,并随机选择了 8
作为示例。
这个解决方案有几个问题。第一个也是显而易见的是,这依赖于 LineChart
的内部实现。第二个是真正脆弱性的来源。对数据的任何更改,无论是轴的值范围、图表的宽度或高度,还是几乎 任何导致图表重绘的东西 都会导致重新计算 Path
并重新绘制。这意味着如果您使用此解决方案,则每次 图表重新绘制时都必须重新应用修改。
我制作了一个 GapLineChart,当 Slaw 看到 Double.NaN 时,它会自动设置一个 MoveTo,就像 Slaw 指向它一样。你可以在这里找到它 https://gist.github.com/sirolf2009/ae8a7897b57dcf902b4ed747b05641f9。查看第一条评论以获取示例
我需要调整 LineChart 以绘制线段,因此每对数据点创建一个线段。通过扩展 LineChart
并覆盖 layoutPlotChildren
函数非常简单,尽管我不得不删除一个动画 y
-scaling 变量,因为它在 super 中是 private
class,但我没有为图表制作动画,所以没问题。无论如何,这里是 Kotlin 版本(您可以轻松适应 Java,或者只需复制 Java LineChart
layoutPlotChildren
代码并适应)。很容易适应您的情况,只需在最后更改 for
循环的内容即可。
import javafx.scene.chart.Axis
import javafx.scene.chart.LineChart
import javafx.scene.shape.LineTo
import javafx.scene.shape.MoveTo
import javafx.scene.shape.Path
import java.util.*
import kotlin.collections.ArrayList
class GapLineChart<X, Y>(xAxis: Axis<X>?, yAxis: Axis<Y>?) : LineChart<X, Y>(xAxis, yAxis) {
public override fun layoutPlotChildren() {
val constructedPath = ArrayList<LineTo>(data.size)
for (seriesIndex in 0 until data.size) {
val series = data[seriesIndex]
if (series.node is Path) {
val seriesLine = (series.node as Path).elements
val it = getDisplayedDataIterator(series)
seriesLine.clear()
constructedPath.clear()
var parity = true
while (it.hasNext()) {
val item = it.next()
val x = xAxis.getDisplayPosition(item.xValue)
val y = yAxis.getDisplayPosition(yAxis.toRealValue(yAxis.toNumericValue(item.yValue)))
if (java.lang.Double.isNaN(x) || java.lang.Double.isNaN(y)) {
continue
}
if(parity)
constructedPath.add(LineTo(x, y))
val symbol = item.node
if (symbol != null) {
val w = symbol.prefWidth(-1.0)
val h = symbol.prefHeight(-1.0)
symbol.resizeRelocate(x - w / 2, y - h / 2, w, h)
}
}
when (axisSortingPolicy) {
SortingPolicy.X_AXIS -> constructedPath.sortWith(Comparator { e1, e2 -> java.lang.Double.compare(e1.x, e2.x) })
SortingPolicy.Y_AXIS -> constructedPath.sortWith(Comparator { e1, e2 -> java.lang.Double.compare(e1.y, e2.y) })
}
if (!constructedPath.isEmpty()) {
for (i in 0 until constructedPath.size-1 step 2) {
seriesLine.add(MoveTo(constructedPath[i].x, constructedPath[i].y))
seriesLine.add(LineTo(constructedPath[i+1].x, constructedPath[i+1].y))
}
}
}
}
}
}