JavaFX图表看起来不错!

JavaFX Charts Look Pretty Good!

当我在大学进行三年级项目时,需要在用户界面中使用漂亮的图表。 现在,我已经长大了(将近两年!),我希望我会转向JavaFX中的图表,而不是我使用的外观难看的JChart2D API。 现在不要误会我的意思,我不讨厌JChart2D,因为它易于使用,并且当时我对此感到非常满意。 但是您可以在JavaFX中使用的图表看起来确实不错,并且默认情况下它们甚至还带有一些精美的动画。 好的,我已经完成了对这些图形的宣传,因此,我将向您展示我编写的一个示例应用程序,希望您对它们的理解与我相同。

AreaChart

在继续之前,建议您阅读使用JavaFX的基础知识。 我的其他文章使用JavaFX制作Apps对此进行了介绍。

在本文中,我将介绍aLineChartandAreaChart的基本用法。 该代码非常简单,只需要在Controllerandfxml中进行一些设置即可。 您需要做的就是将图表添加到fxmlcode中,给它afx:id,根据自己的喜好设置其属性,然后就完成了。

1
2
3
4
5
6
7
8
9
10
11
<LineChart fx:id="lineGraph" createSymbols="false" legendVisible="false" prefHeight="372.0" prefWidth="423.0" visible="false">
  <xAxis>
    <NumberAxis autoRanging="false" lowerBound="-10" side="BOTTOM" tickUnit="1" upperBound="10" />
  </xAxis>
  <yAxis>
    <NumberAxis autoRanging="false" lowerBound="-10" side="LEFT" tickUnit="1" upperBound="10" />
  </yAxis>
  <cursor>
    <Cursor fx:constant="CROSSHAIR" />
  </cursor>
</LineChart>

这是LineChart的设置。 它定义了轴的上下边界,这将使图表看起来更像图形。 通过设置x或y轴的下限,它将使图表自动绘制线以表示将通过坐标(0,0)的轴。 ThetickUnit表示图表上网格线的分隔方式,并且禁用了自动量程以防止图表调整自身大小以最适合图表上的内容。 最后,已经定义了一个游标。 还有一些其他游标可以使用,但我认为十字光标看起来最好。

既然您知道如何创建图表,您将需要知道如何在其上绘制一些点(除非您对它为空感到满意)。

1
2
3
4
5
6
7
public void plotLine() {
    final XYChart.Series<Double, Double> series = new XYChart.Series<Double, Double>();
    for (double x = -range; x <= range; x = x + 0.01) {
        series.getData().add(new XYChart.Data<Double, Double>(x, Math.pow(x, 2)));
    }
    graph.getData().add(series);
}

在这里,我们正在创建一个新系列对象,其中将包含绘制的点。

1
final XYChart.Series<Double, Double> series = new XYChart.Series<Double, Double>()

然后,将图添加到系列中。

1
series.getData().add(new XYChart.Data<Double, Double>(x, Math.pow(x, 2)))

绘制的坐标以(x,y)的形式存储,因此在上面的示例中,x坐标的值为x,y坐标的值为x ^ 2。

最后一步是将系列添加到图表中。

1
graph.getData().add(series)

完成此操作后,这些点将被绘制到图表上,并将在每个图之间连接在一起。 将绘图(XYChart.Data)添加到序列中的顺序无关紧要,因为它将使最接近的绘图相互连接,但是如果从循环中添加绘图则无关紧要。

现在已经涵盖了使用这些图表的基础知识,让我们来看一个更大的示例。 以下是您需要设置的所有代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainAppLauncher extends Application {

    public static void main(String[] args) {
        Application.launch(MainAppLauncher.class, args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        try {
            Parent root = FXMLLoader.load(getClass().getClassLoader()
            .getResource("lankydan/tutorials/fxml/MainApp.fxml"));
            stage.setScene(new Scene(root));
            stage.setTitle("JavaFX Graph Example");
            stage.show();
        } catch (Exception e) {
            System.out.print(e);
        }
    }
}

MainAppLauncheris用于加载运行JavaFX代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public class MainAppController implements Initializable {

    @FXML
    private LineChart<Double, Double> lineGraph;

    @FXML
    private AreaChart<Double, Double> areaGraph;

    @FXML
    private Button lineGraphButton;

    @FXML
    private Button areaGraphButton;

    @FXML
    private Button xyButton;

    @FXML
    private Button xyButton2;

    @FXML
    private Button squaredButton;

    @FXML
    private Button squaredButton2;

    @FXML
    private Button cubedButton;

    @FXML
    private Button cubedButton2;

    @FXML
    private Button clearButton;

    private MyGraph mathsGraph;
    private MyGraph areaMathsGraph;

    @Override
    public void initialize(final URL url, final ResourceBundle rb) {
        mathsGraph = new MyGraph(lineGraph, 10);
        areaMathsGraph = new MyGraph(areaGraph, 10);
    }

    @FXML
    private void handleLineGraphButtonAction(final ActionEvent event) {
        lineGraph.setVisible(true);
        areaGraph.setVisible(false);
    }

    @FXML
    private void handleAreaGraphButtonAction(final ActionEvent event) {
        areaGraph.setVisible(true);
        lineGraph.setVisible(false);
    }

    @FXML
    private void handleXYButtonAction(final ActionEvent event) {
        plotLine(x -> x);
    }

    private void plotLine(Function<Double, Double> function) {
        if (lineGraph.isVisible()) {
            mathsGraph.plotLine(function);
        } else {
            areaMathsGraph.plotLine(function);
        }
    }

    @FXML
    private void handleXYButton2Action(final ActionEvent event) {
        plotLine(x -> x - 3);
    }

    @FXML
    private void handleSquaredButtonAction(final ActionEvent event) {
        plotLine(x -> Math.pow(x, 2));
    }

    @FXML
    private void handleSquaredButton2Action(final ActionEvent event) {
        plotLine(x -> Math.pow(x, 2) + 2);
    }

    @FXML
    private void handleCubedButtonAction(final ActionEvent event) {
        plotLine(x -> Math.pow(x, 3));
    }

    @FXML
    private void handleCubedButton2Action(final ActionEvent event) {
        plotLine(x -> Math.pow(x - 3, 3) - 1);
    }

    @FXML
    private void handleClearButtonAction(final ActionEvent event) {
        clear();
    }

    private void clear() {
        if (lineGraph.isVisible()) {
            mathsGraph.clear();
        } else {
            areaMathsGraph.clear();
        }
    }
}

MainAppController是此代码的核心,它通过处理由按钮按下触发的ActionEvents来控制图表,在此示例中,这导致将线条绘制到图表上。 在本示例中,我还使用了somelambda表达式使代码更短,更有趣。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MyGraph {

    private XYChart<Double, Double> graph;
    private double range;

    public MyGraph(final XYChart<Double, Double> graph, final double range) {
        this.graph = graph;
        this.range = range;
    }

    public void plotLine(final Function<Double, Double> function) {
        final XYChart.Series<Double, Double> series = new XYChart.Series<Double, Double>();
        for (double x = -range; x <= range; x = x + 0.01) {
            plotPoint(x, function.apply(x), series);
        }
        graph.getData().add(series);
    }

    private void plotPoint(final double x, final double y,
    final XYChart.Series<Double, Double> series) {
        series.getData().add(new XYChart.Data<Double, Double>(x, y));
    }

    public void clear() {
        graph.getData().clear();
    }
}

MyGraph是一个包装器对象,它包含一个anananchartchart,两者都包含LineChartandAreaChartare,并具有一些绘制和清除线条的方法。 这段代码可能在MainAppController中,因为其中没有太多代码,但是看起来却很整洁。 它使用上面显示的plotLinecode,尽管它分为两个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.Cursor?>
<?import javafx.scene.chart.AreaChart?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>

<AnchorPane maxHeight="400.0" maxWidth="600.0" minHeight="400.0" minWidth="600.0" prefHeight="400.0" prefWidth="600.0" styleClass="root" stylesheets="lankydan/tutorials/fxml/css.css" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="lankydan.tutorials.fxml.controller.MainAppController">
   <children>
    <AnchorPane layoutX="416.0" minHeight="0.0" minWidth="0.0" prefHeight="400.0" prefWidth="185.0">
         <children>
            <VBox prefHeight="398.0" prefWidth="183.0">
               <children>
                  <Button mnemonicParsing="false" onAction="#handleXYButtonAction" prefHeight="66.0" prefWidth="193.0" text="y=x" fx:id="xyButton" />
                  <Button mnemonicParsing="false" onAction="#handleXYButton2Action" prefHeight="66.0" prefWidth="207.0" text="y=x-3" fx:id="xyButton2" />
                  <Button fx:id="squaredButton" mnemonicParsing="false" onAction="#handleSquaredButtonAction" prefHeight="67.0" prefWidth="220.0" text="y=x^2" />
                  <Button fx:id="squaredButton2" mnemonicParsing="false" onAction="#handleSquaredButton2Action" prefHeight="67.0" prefWidth="232.0" text="y=x^2+2" />
                  <Button fx:id="cubedButton" mnemonicParsing="false" onAction="#handleCubedButtonAction" prefHeight="67.0" prefWidth="236.0" text="y=x^3" />
                  <Button fx:id="cubedButton2" mnemonicParsing="false" onAction="#handleCubedButton2Action" prefHeight="67.0" prefWidth="266.0" text="y=(x-3)^3-1" />
                  <Button fx:id="clearButton" mnemonicParsing="false" onAction="#handleClearButtonAction" prefHeight="67.0" prefWidth="266.0" text="clear" />
               </children>
            </VBox>
         </children>
      </AnchorPane>
      <AnchorPane layoutX="-7.0" prefHeight="400.0" prefWidth="420.0">
         <children>
            <LineChart fx:id="lineGraph" legendVisible="false" prefHeight="372.0" prefWidth="423.0" visible="false">
              <xAxis>
              <NumberAxis autoRanging="false" lowerBound="-10" side="BOTTOM" tickUnit="1" upperBound="10" />
              </xAxis>
              <yAxis>
                <NumberAxis autoRanging="false" lowerBound="-10" side="LEFT" tickUnit="1" upperBound="10" />
              </yAxis>
              <cursor>
                 <Cursor fx:constant="CROSSHAIR" />
              </cursor>
            </LineChart>
            <AreaChart fx:id="areaGraph" legendVisible="false" prefHeight="372.0" prefWidth="423.0">
              <xAxis>
              <NumberAxis autoRanging="false" lowerBound="-10" side="BOTTOM" tickUnit="1" upperBound="10" />
              </xAxis>
              <yAxis>
                <NumberAxis autoRanging="false" lowerBound="-10" side="LEFT" tickUnit="1" upperBound="10" />
              </yAxis>
              <cursor>
                 <Cursor fx:constant="CROSSHAIR" />
              </cursor>
            </AreaChart>
            <Button fx:id="lineGraphButton" onAction="#handleLineGraphButtonAction" layoutX="35.0" layoutY="366.0" mnemonicParsing="false" prefHeight="29.0" prefWidth="185.0" text="Line Graph" />
            <Button layoutX="224.0" onAction="#handleAreaGraphButtonAction" layoutY="366.0" mnemonicParsing="false" prefHeight="29.0" prefWidth="185.0" text="Area Graph" />
         </children>
      </AnchorPane>
   </children>
</AnchorPane>

MainApp.fxml包含所有用于设置应用程序外观的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#pane, .root{
    -fx-background-color: #353434;
    -fx-foreground-color: #353434;
}

.default-color0.chart-series-line { -fx-stroke: #2c2e4e; }

.chart-vertical-zero-line {
    -fx-stroke: white;
}
.chart-horizontal-zero-line {
    -fx-stroke: white;
}

.chart-plot-background {
    -fx-background-color: #575758;
    -fx-foreground-color: white;
    -fx-stroke: white;
}

.chart-vertical-grid-lines {
    -fx-stroke: #898887;
}
.chart-horizontal-grid-lines {
    -fx-stroke: #898887;
}
.chart-alternative-row-fill {
    -fx-fill: transparent;
    -fx-stroke: transparent;
    -fx-stroke-width: 0;
}

.axis {
-fx-stroke: white;
-fx-fill: white;
    -fx-font-size: 1.4em;    
    -fx-tick-label-fill: white;
    -fx-font-family: Tahoma;
    -fx-tick-length: 0;
    -fx-minor-tick-length: 0;
}

.background {
    -fx-background-color: #674A44;
    -fx-foreground-color: #353434;
}

.button {
    -fx-padding: 5 22 5 22;  
    -fx-border-color: #353434;
    -fx-border-width: 0;
    -fx-background-radius: 0;
    -fx-background-color: derive(#353434,20%);
    -fx-font-family:"Segoe UI", Helvetica, Arial, sans-serif;
    -fx-font-size: 11pt;
    -fx-text-fill: #d8d8d8;
    -fx-background-insets: 0 0 0 0, 0, 1, 2;
}

.button:hover {
    -fx-background-color: #3a3a3a;
}

.button:pressed, .button:default:hover:pressed {
  -fx-background-color: #bdbcbc;
  -fx-text-fill: black;
}

.button:disabled, .button:default:disabled {
    -fx-opacity: 0.4;
    -fx-background-color: #353434;
    -fx-text-fill: white;
}

.button:default {
    -fx-background-color: -fx-focus-color;
    -fx-text-fill: #ffffff;
}

.button:default:hover {
    -fx-background-color: derive(-fx-focus-color,30%);
}

.text-area .content {
-fx-background-color: #575758;
}

Css.cssis用于此应用程序的样式表,在MainApp.fxml内部引用。

如果您尝试自己运行它,则需要记住一些更改。 否则,您将遇到一些错误。 确保您的文件结构合理或了解文件路径的工作方式。 下面是我如何构造文件的图片。

Project Structure

整理完项目结构后,请记住重命名代码中的路径,以使其与您选择的结构和名称匹配。 这包括MainAppLauncher中的路径和MainApp.fxml中controllerandcss的路径。

一旦正确地将所有内容组合在一起,您就应该能够运行它,它将看起来像这样。

Area Chart

然后按"线图"按钮,它看起来像这样。

Line Chart

因此,既然您已经了解了JavaFX中必须提供的图表的一点点,那么在编写一些自己的应用程序时可以考虑使用它。 它们非常容易设置,并且默认情况下带有一些不错的样式和动画。 我将在以后再写几篇文章,介绍如何使用其他一些图表。