和平在你身上!
以下是巨型软件中趋势的图像,其中绘制了一些实时数据和历史数据。有两个笔注册,每个笔的单独轴在同一趋势上定义。
我一直试图在JavaFX 2.0图表中做到这一点。我已经能够绘制如下实时图表:
我一直在使用JavaFX研究多轴并且可以找到this链接,但我认为这是使用FXML的旧版JavaFX。但是,我正在使用JavaFX常规类来完成此任务。
HELP !!!
答案 0 :(得分:10)
Here您可以找到我的解决方案 - MultipleAxesLineChart
。它不是通用的,只是符合我的需要,但我认为它可以通过StackPane
很好地了解它是如何完成的。
答案 1 :(得分:5)
将两个图表放在StackPane中。
使用顶部css lookups上的chart来翻译(使用translate-x和translate-y)它的轴和标签,以便可以独立于底部图表进行读取。保留顶部图表的数据图,使其覆盖底部图表。修改颜色和图例(或混合图表样式,例如线条和条形图),以便明显哪个数据图属于哪个系列。
上面介绍了一些技术:
答案 2 :(得分:0)
public class MultipleAxesLineChart extends StackPane {
private final LineChart baseChart;
private final ObservableList<LineChart> backgroundCharts = FXCollections.observableArrayList();
private final Map<LineChart, Color> chartColorMap = new HashMap<>();
private final double yAxisWidth = 60;
private final AnchorPane detailsWindow;
private final double yAxisSeparation = 20;
private double strokeWidth = 0.3;
public MultipleAxesLineChart(LineChart baseChart, Color lineColor) {
this(baseChart, lineColor, null);
}
public MultipleAxesLineChart(LineChart baseChart, Color lineColor, Double strokeWidth) {
if (strokeWidth != null) {
this.strokeWidth = strokeWidth;
}
this.baseChart = baseChart;
chartColorMap.put(baseChart, lineColor);
styleBaseChart(baseChart);
styleChartLine(baseChart, lineColor);
setFixedAxisWidth(baseChart);
setAlignment(Pos.CENTER_LEFT);
backgroundCharts.addListener((Observable observable) -> rebuildChart());
detailsWindow = new AnchorPane();
bindMouseEvents(baseChart, this.strokeWidth);
rebuildChart();
}
private void bindMouseEvents(LineChart baseChart, Double strokeWidth) {
final DetailsPopup detailsPopup = new DetailsPopup();
getChildren().add(detailsWindow);
detailsWindow.getChildren().add(detailsPopup);
detailsWindow.prefHeightProperty().bind(heightProperty());
detailsWindow.prefWidthProperty().bind(widthProperty());
detailsWindow.setMouseTransparent(true);
setOnMouseMoved(null);
setMouseTransparent(false);
final Axis xAxis = baseChart.getXAxis();
final Axis yAxis = baseChart.getYAxis();
final Line xLine = new Line();
final Line yLine = new Line();
yLine.setFill(Color.GRAY);
xLine.setFill(Color.GRAY);
yLine.setStrokeWidth(strokeWidth/2);
xLine.setStrokeWidth(strokeWidth/2);
xLine.setVisible(false);
yLine.setVisible(false);
final Node chartBackground = baseChart.lookup(".chart-plot-background");
for (Node n: chartBackground.getParent().getChildrenUnmodifiable()) {
if (n != chartBackground && n != xAxis && n != yAxis) {
n.setMouseTransparent(true);
}
}
chartBackground.setCursor(Cursor.CROSSHAIR);
chartBackground.setOnMouseEntered((event) -> {
chartBackground.getOnMouseMoved().handle(event);
detailsPopup.setVisible(true);
xLine.setVisible(true);
yLine.setVisible(true);
detailsWindow.getChildren().addAll(xLine, yLine);
});
chartBackground.setOnMouseExited((event) -> {
detailsPopup.setVisible(false);
xLine.setVisible(false);
yLine.setVisible(false);
detailsWindow.getChildren().removeAll(xLine, yLine);
});
chartBackground.setOnMouseMoved(event -> {
double x = event.getX() + chartBackground.getLayoutX();
double y = event.getY() + chartBackground.getLayoutY();
xLine.setStartX(10);
xLine.setEndX(detailsWindow.getWidth()-10);
xLine.setStartY(y+5);
xLine.setEndY(y+5);
yLine.setStartX(x+5);
yLine.setEndX(x+5);
yLine.setStartY(10);
yLine.setEndY(detailsWindow.getHeight()-10);
detailsPopup.showChartDescrpition(event);
if (y + detailsPopup.getHeight() + 10 < getHeight()) {
AnchorPane.setTopAnchor(detailsPopup, y+10);
} else {
AnchorPane.setTopAnchor(detailsPopup, y-10-detailsPopup.getHeight());
}
if (x + detailsPopup.getWidth() + 10 < getWidth()) {
AnchorPane.setLeftAnchor(detailsPopup, x+10);
} else {
AnchorPane.setLeftAnchor(detailsPopup, x-10-detailsPopup.getWidth());
}
});
}
private void styleBaseChart(LineChart baseChart) {
baseChart.setCreateSymbols(false);
baseChart.setLegendVisible(false);
baseChart.getXAxis().setAutoRanging(false);
baseChart.getXAxis().setAnimated(false);
baseChart.getYAxis().setAnimated(false);
}
private void setFixedAxisWidth(LineChart chart) {
chart.getYAxis().setPrefWidth(yAxisWidth);
chart.getYAxis().setMaxWidth(yAxisWidth);
}
private void rebuildChart() {
getChildren().clear();
getChildren().add(resizeBaseChart(baseChart));
for (LineChart lineChart : backgroundCharts) {
getChildren().add(resizeBackgroundChart(lineChart));
}
getChildren().add(detailsWindow);
}
private Node resizeBaseChart(LineChart lineChart) {
HBox hBox = new HBox(lineChart);
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.prefHeightProperty().bind(heightProperty());
hBox.prefWidthProperty().bind(widthProperty());
lineChart.minWidthProperty().bind(widthProperty().subtract((yAxisWidth+yAxisSeparation)*backgroundCharts.size()));
lineChart.prefWidthProperty().bind(widthProperty().subtract((yAxisWidth+yAxisSeparation)*backgroundCharts.size()));
lineChart.maxWidthProperty().bind(widthProperty().subtract((yAxisWidth+yAxisSeparation)*backgroundCharts.size()));
return lineChart;
}
private Node resizeBackgroundChart(LineChart lineChart) {
HBox hBox = new HBox(lineChart);
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.prefHeightProperty().bind(heightProperty());
hBox.prefWidthProperty().bind(widthProperty());
hBox.setMouseTransparent(true);
lineChart.minWidthProperty().bind(widthProperty().subtract((yAxisWidth + yAxisSeparation) * backgroundCharts.size()));
lineChart.prefWidthProperty().bind(widthProperty().subtract((yAxisWidth + yAxisSeparation) * backgroundCharts.size()));
lineChart.maxWidthProperty().bind(widthProperty().subtract((yAxisWidth + yAxisSeparation) * backgroundCharts.size()));
lineChart.translateXProperty().bind(baseChart.getYAxis().widthProperty());
lineChart.getYAxis().setTranslateX((yAxisWidth + yAxisSeparation) * backgroundCharts.indexOf(lineChart));
return hBox;
}
public void addSeries(XYChart.Series series, Color lineColor) {
NumberAxis yAxis = new NumberAxis();
NumberAxis xAxis = new NumberAxis();
// style x-axis
xAxis.setAutoRanging(false);
xAxis.setVisible(false);
xAxis.setOpacity(0.0); // somehow the upper setVisible does not work
xAxis.lowerBoundProperty().bind(((NumberAxis) baseChart.getXAxis()).lowerBoundProperty());
xAxis.upperBoundProperty().bind(((NumberAxis) baseChart.getXAxis()).upperBoundProperty());
xAxis.tickUnitProperty().bind(((NumberAxis) baseChart.getXAxis()).tickUnitProperty());
// style y-axis
yAxis.setSide(Side.RIGHT);
yAxis.setLabel(series.getName());
// create chart
LineChart lineChart = new LineChart(xAxis, yAxis);
lineChart.setAnimated(false);
lineChart.setLegendVisible(false);
lineChart.getData().add(series);
styleBackgroundChart(lineChart, lineColor);
setFixedAxisWidth(lineChart);
chartColorMap.put(lineChart, lineColor);
backgroundCharts.add(lineChart);
}
private void styleBackgroundChart(LineChart lineChart, Color lineColor) {
styleChartLine(lineChart, lineColor);
Node contentBackground = lineChart.lookup(".chart-content").lookup(".chart-plot-background");
contentBackground.setStyle("-fx-background-color: transparent;");
lineChart.setVerticalZeroLineVisible(false);
lineChart.setHorizontalZeroLineVisible(false);
lineChart.setVerticalGridLinesVisible(false);
lineChart.setHorizontalGridLinesVisible(false);
lineChart.setCreateSymbols(false);
}
private String toRGBCode(Color color) {
return String.format("#%02X%02X%02X",
(int) (color.getRed() * 255),
(int) (color.getGreen() * 255),
(int) (color.getBlue() * 255));
}
private void styleChartLine(LineChart chart, Color lineColor) {
chart.getYAxis().lookup(".axis-label").setStyle("-fx-text-fill: " + toRGBCode(lineColor) + "; -fx-font-weight: bold;");
Node seriesLine = chart.lookup(".chart-series-line");
seriesLine.setStyle("-fx-stroke: " + toRGBCode(lineColor) + "; -fx-stroke-width: " + strokeWidth + ";");
}
public Node getLegend() {
HBox hBox = new HBox();
final CheckBox baseChartCheckBox = new CheckBox(baseChart.getYAxis().getLabel());
baseChartCheckBox.setSelected(true);
baseChartCheckBox.setStyle("-fx-text-fill: " + toRGBCode(chartColorMap.get(baseChart)) + "; -fx-font-weight: bold;");
baseChartCheckBox.setDisable(true);
baseChartCheckBox.getStyleClass().add("readonly-checkbox");
baseChartCheckBox.setOnAction(event -> baseChartCheckBox.setSelected(true));
hBox.getChildren().add(baseChartCheckBox);
for (final LineChart lineChart : backgroundCharts) {
CheckBox checkBox = new CheckBox(lineChart.getYAxis().getLabel());
checkBox.setStyle("-fx-text-fill: " + toRGBCode(chartColorMap.get(lineChart)) + "; -fx-font-weight: bold");
checkBox.setSelected(true);
checkBox.setOnAction(event -> {
if (backgroundCharts.contains(lineChart)) {
backgroundCharts.remove(lineChart);
} else {
backgroundCharts.add(lineChart);
}
});
hBox.getChildren().add(checkBox);
}
hBox.setAlignment(Pos.CENTER);
hBox.setSpacing(20);
hBox.setStyle("-fx-padding: 0 10 20 10");
return hBox;
}
private class DetailsPopup extends VBox {
private DetailsPopup() {
setStyle("-fx-border-width: 1px; -fx-padding: 5 5 5 5px; -fx-border-color: gray; -fx-background-color: whitesmoke;");
setVisible(false);
}
public void showChartDescrpition(MouseEvent event) {
getChildren().clear();
Long xValueLong = Math.round((double)baseChart.getXAxis().getValueForDisplay(event.getX()));
HBox baseChartPopupRow = buildPopupRow(event, xValueLong, baseChart);
if (baseChartPopupRow != null) {
getChildren().add(baseChartPopupRow);
}
for (LineChart lineChart : backgroundCharts) {
HBox popupRow = buildPopupRow(event, xValueLong, lineChart);
if (popupRow == null) continue;
getChildren().add(popupRow);
}
}
private HBox buildPopupRow(MouseEvent event, Long xValueLong, LineChart lineChart) {
Label seriesName = new Label(lineChart.getYAxis().getLabel());
seriesName.setTextFill(chartColorMap.get(lineChart));
Number yValueForChart = getYValueForX(lineChart, xValueLong.intValue());
if (yValueForChart == null) {
return null;
}
Number yValueLower = Math.round(normalizeYValue(lineChart, event.getY() - 10));
Number yValueUpper = Math.round(normalizeYValue(lineChart, event.getY() + 10));
Number yValueUnderMouse = Math.round((double) lineChart.getYAxis().getValueForDisplay(event.getY()));
// make series name bold when mouse is near given chart's line
if (isMouseNearLine(yValueForChart, yValueUnderMouse, Math.abs(yValueLower.doubleValue()-yValueUpper.doubleValue()))) {
seriesName.setStyle("-fx-font-weight: bold");
}
HBox popupRow = new HBox(10, seriesName, new Label("["+yValueForChart+"]"));
return popupRow;
}
private double normalizeYValue(LineChart lineChart, double value) {
Double val = (Double) lineChart.getYAxis().getValueForDisplay(value);
if (val == null) {
return 0;
} else {
return val;
}
}
private boolean isMouseNearLine(Number realYValue, Number yValueUnderMouse, Double tolerance) {
return (Math.abs(yValueUnderMouse.doubleValue() - realYValue.doubleValue()) < tolerance);
}
public Number getYValueForX(LineChart chart, Number xValue) {
List<XYChart.Data> dataList = ((List<XYChart.Data>)((XYChart.Series)chart.getData().get(0)).getData());
for (XYChart.Data data : dataList) {
if (data.getXValue().equals(xValue)) {
return (Number)data.getYValue();
}
}
return null;
}
}
}
MultipleAxisMainChart:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.function.Function;
public class MultipleAxesLineChartMain extends Application {
public static final int X_DATA_COUNT = 3600;
@Override
public void start(Stage primaryStage) throws Exception{
NumberAxis xAxis = new NumberAxis(0, X_DATA_COUNT, 200);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Series 1");
LineChart baseChart = new LineChart(xAxis, yAxis);
baseChart.getData().add(prepareSeries("Series 1", (x) -> (double)x));
MultipleAxesLineChart chart = new MultipleAxesLineChart(baseChart, Color.RED);
chart.addSeries(prepareSeries("Series 2", (x) -> (double)x*x),Color.BLUE);
chart.addSeries(prepareSeries("Series 3", (x) -> (double)-x*x),Color.GREEN);
chart.addSeries(prepareSeries("Series 4", (x) -> ((double) (x-250))*x),Color.DARKCYAN);
chart.addSeries(prepareSeries("Series 5", (x) -> ((double)(x+100)*(x-200))),Color.BROWN);
primaryStage.setTitle("MultipleAxesLineChart");
BorderPane borderPane = new BorderPane();
borderPane.setCenter(chart);
borderPane.setBottom(chart.getLegend());
Scene scene = new Scene(borderPane, 1024, 600);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private XYChart.Series<Number, Number> prepareSeries(String name, Function<Integer, Double> function) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName(name);
for (int i = 0; i < X_DATA_COUNT; i++) {
series.getData().add(new XYChart.Data<>(i, function.apply(i)));
}
return series;
}
public static void main(String[] args) {
launch(args);
}
}
答案 3 :(得分:0)
根据Maciej的回答,对UI进行了一些更改,使左右坐标轴具有相同的yaxis。 preview