Статьи

JavaFX 2 XYCharts и Java 7 Особенности

Одна из моих любимых функций JavaFX 2 – это стандартные диаграммы, которые он предоставляет в своем пакете javafx.scene.chart . Этот пакет предоставляет несколько различных типов диаграмм из коробки. Все, кроме одного ( PieChart ), представляют собой «двухосные диаграммы» (конкретные реализации XYChart ). В этом посте я рассматриваю общность этих специализаций XYChart . Попутно я рассмотрю несколько полезных функций Java 7 .

Диаграмма классов UML для типов ключевых диаграмм в пакете javafx.scene.chart показана далее. Обратите внимание, что AreaChart , StackedAreaChart , BarChart , StackedBarChart , BubbleChart , LineChart и ScatterChart все расширяют XYChart .

Как показано на PieChart выше UML-диаграмме (созданной с использованием JDeveloper), PieChart напрямую расширяет диаграмму, в то время как все другие типы диаграмм расширяют XYChart . Поскольку все типы диаграмм, кроме PieChart расширяют XYChart , они имеют некоторые общие черты. Например, все они представляют собой двухосные диаграммы с горизонтальной (‘x’) и вертикальной (‘y’) осями. Как правило, они позволяют указывать данные в одном формате (структуре данных) для всех диаграмм XY. Оставшаяся часть этого поста демонстрирует возможность использования одних и тех же данных для большинства XYChart .

Основное использование диаграммы – отображение данных, поэтому следующий листинг кода указывает на получение данных из образца схемы « hr » в базе данных Oracle. Обратите внимание, что JDBC_URL, USERNAME, PASSWORD и AVG_SALARIES_PER_DEPARTMENT_QUERY являются константными строками, используемыми в соединении JDBC и для запроса.

getAverageDepartmentsSalaries ()

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * Provide average salary per department name.
 *
 * @return Map of department names to average salary per department.
 */
public Map<String, Double> getAverageDepartmentsSalaries()
{
   final Map<String, Double> averageSalaryPerDepartment = new HashMap<>();
   try (final Connection connection = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);
        final Statement statement = connection.createStatement();
        final ResultSet rs = statement.executeQuery(AVG_SALARIES_PER_DEPARTMENT_QUERY))
   {
      while (rs.next())
      {
         final String departmentName = rs.getString(COLUMN_DEPARTMENT_NAME);
         final Double salaryAverage = rs.getDouble(ALIAS_AVERAGE_SALARY);
         averageSalaryPerDepartment.put(departmentName, salaryAverage);
      }
   }
   catch (SQLException sqlEx)
   {
      LOGGER.log(
         Level.SEVERE,
         'Unable to get average salaries per department - {0}', sqlEx.toString());
   }
   return averageSalaryPerDepartment;
}

Приведенный выше фрагмент кода Java использует JDBC для извлечения данных для заполнения Строки сопоставления названий отделов со средней зарплатой сотрудников в каждом отделе. В этом коде есть несколько удобных функций Java 7. Небольшой особенностью является предполагаемая универсальная параметризованная типизация оператора diamond, используемая с объявлением локальной переменной averageSalaryPerDepartment (строка 8). Это небольшой кусочек синтаксического сахара, но он делает код более лаконичным.

Более важной особенностью Java 7 является использование оператора try-with-resources для обработки ресурсов Connection , Statement и ResultSet (строки 9-11). Это гораздо более приятный способ справиться с открытием и закрытием этих ресурсов, даже несмотря на исключения, чем это было ранее необходимо при использовании JDBC. Страница Java Tutorials в заявлении try-with-resources объявляет, что этот оператор «гарантирует, что каждый ресурс закрыт в конце оператора» и что каждый ресурс «будет закрыт, независимо от того, завершается ли оператор try нормально или внезапно». На странице также отмечается, что когда в одном и том же операторе указано несколько ресурсов, как это сделано в приведенном выше коде, «методы закрытия ресурсов вызываются в порядке, обратном их созданию».

Данные, извлеченные из базы данных, могут быть помещены в соответствующую структуру данных для поддержки использования большинством XYCharts. Это показано в следующем методе.

ChartMaker.createXyChartDataForAverageDepartmentSalary (Карта)

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * Create XYChart Data representing average salary per department name.
 *
 * @param newAverageSalariesPerDepartment Map of department name (keys) to
 *    average salary for each department (values).
 * @return XYChart Data representing average salary per department.
 */
public static ObservableList<XYChart.Series<String, Double>> createXyChartDataForAverageDepartmentSalary(
   final Map<String, Double> newAverageSalariesPerDepartment)
{
   final Series<String, Double> series = new Series<>();
   series.setName('Departments');
   for (final Map.Entry<String, Double> entry : newAverageSalariesPerDepartment.entrySet())
   {
      series.getData().add(new XYChart.Data<>(entry.getKey(), entry.getValue()));
   }
   final ObservableList<XYChart.Series<String, Double>> chartData =
      FXCollections.observableArrayList();
 
   chartData.add(series);
   return chartData;
}

Только что показанный метод помещает извлеченные данные в структуру данных, которая может использоваться почти всеми XYChart основе XYChart . Полученные данные теперь упакованы в наблюдаемую коллекцию JavaFX, поэтому диаграммы могут быть легко созданы. В следующем фрагменте кода показаны методы для создания нескольких диаграмм на основе XYChart (Площадь, Линия, Пузырь, Линия и Разброс). Обратите внимание, насколько они все похожи и как используют одни и те же данные, предоставленные одним и тем же методом. Диаграммы StackedBar и StackedArea также могут использовать аналогичные данные, но они здесь не показаны, поскольку они не интересны для отдельной серии данных, используемых в этом примере.

Методы генерации XYChart, кроме BubbleChart и Stacked Charts

01
02
03
04
05
06
07
08
09
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
private XYChart<String, Double> generateAreaChart(
   final Axis<String> xAxis, final Axis<Double> yAxis)
{
   final AreaChart<String, Double> areaChart =
      new AreaChart<>(
         xAxis, yAxis,
         ChartMaker.createXyChartDataForAverageDepartmentSalary(
            this.databaseAccess.getAverageDepartmentsSalaries()));
   return areaChart;
}
 
private XYChart<String, Double> generateBarChart(
   final Axis<String> xAxis, final Axis<Double> yAxis)
{
   final BarChart<String, Double> barChart =
      new BarChart<>(
         xAxis, yAxis,
         ChartMaker.createXyChartDataForAverageDepartmentSalary(
            this.databaseAccess.getAverageDepartmentsSalaries()));
   return barChart;
}
 
private XYChart<Number, Number> generateBubbleChart(
   final Axis<String> xAxis, final Axis<Double> yAxis)
{
   final Axis<Number> deptIdXAxis = new NumberAxis();
   deptIdXAxis.setLabel('Department ID');
   final BubbleChart<Number, Number> bubbleChart =
      new BubbleChart(
         deptIdXAxis, yAxis,
         ChartMaker.createXyChartDataForAverageDepartmentSalaryById(
            this.databaseAccess.getAverageDepartmentsSalariesById()));
   return bubbleChart;
}
 
private XYChart<String, Double> generateLineChart(
        final Axis<String> xAxis, final Axis<Double> yAxis)
{
   final LineChart<String, Double> lineChart =
      new LineChart<>(
         xAxis, yAxis,
         ChartMaker.createXyChartDataForAverageDepartmentSalary(
            this.databaseAccess.getAverageDepartmentsSalaries()));
   return lineChart;
}
 
private XYChart<String, Double> generateScatterChart(
   final Axis<String> xAxis, final Axis<Double> yAxis)
{
   final ScatterChart<String, Double> scatterChart =
      new ScatterChart<>(
         xAxis, yAxis,
         ChartMaker.createXyChartDataForAverageDepartmentSalary(
            this.databaseAccess.getAverageDepartmentsSalaries()));
   return scatterChart;
}

Эти методы настолько похожи, что я мог бы фактически использовать дескрипторы методов (или более традиционные API-интерфейсы отражения), чтобы рефлексивно вызывать соответствующий конструктор диаграммы, а не использовать отдельные методы. Однако я использую их для своей презентации RMOUG Training Days 2013 в феврале и поэтому хотел оставить конструкторы для конкретных диаграмм на месте, чтобы сделать их более понятными для аудитории.

Единственным исключением из общей обработки типов XYChart является обработка BubbleChart . Эта диаграмма ожидает числовой тип для своей оси X, поэтому приведенные выше данные оси X на основе строк (название отдела) не будут работать. Другой метод (не показанный здесь) предоставляет запрос, который возвращает среднюю зарплату по ID отдела (Long), а не по названию отдела. Немного другой метод generateBubbleChart показан ниже.

generateBubbleChart (Ось, Ось)

01
02
03
04
05
06
07
08
09
10
11
12
private XYChart<Number, Number> generateBubbleChart(
   final Axis<String> xAxis, final Axis<Double> yAxis)
{
   final Axis<Number> deptIdXAxis = new NumberAxis();
   deptIdXAxis.setLabel('Department ID');
   final BubbleChart<Number, Number> bubbleChart =
      new BubbleChart(
         deptIdXAxis, yAxis,
         ChartMaker.createXyChartDataForAverageDepartmentSalaryById(
            this.databaseAccess.getAverageDepartmentsSalariesById()));
   return bubbleChart;
}

Можно было бы написать код для непосредственного вызова каждого из этих различных методов генерации диаграмм, но это дает хороший шанс использовать дескрипторы методов Java 7. Следующий фрагмент кода показывает, как это делается. Этот код не только демонстрирует дескрипторы метода, но также использует механизм обработки исключений multi-catch в Java 7 (строка 77).

01
02
03
04
05
06
07
08
09
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
/**
 * Generate JavaFX XYChart-based chart.
 *
 * @param chartChoice Choice of chart to be generated.
 * @return JavaFX XYChart-based chart; may be null.
 * @throws IllegalArgumentException Thrown if the provided parameter is null.
 */
private XYChart<String, Double> generateChart(final ChartTypes chartChoice)
{
   XYChart<String, Double> chart = null;
   final Axis<String> xAxis = new CategoryAxis();
   xAxis.setLabel('Department Name');
   final Axis<? extends Number> yAxis = new NumberAxis();
   yAxis.setLabel('Average Salary');
   if (chartChoice == null)
   {
      throw new IllegalArgumentException(
         'Provided chart type was null; chart type must be specified.');
   }
   else if (!chartChoice.isXyChart())
   {
      LOGGER.log(
         Level.INFO,
         'Chart Choice {0} {1} an XYChart.',
         new Object[]{chartChoice.name(), chartChoice.isXyChart() ? 'IS' : 'is NOT'});
   }
 
   final MethodHandle methodHandle = buildAppropriateMethodHandle(chartChoice);
   try
   {
      chart =
        methodHandle != null
      ? (XYChart<String, Double>) methodHandle.invokeExact(this, xAxis, yAxis)
      : null;
      chart.setTitle('Average Department Salaries');
   }
   catch (WrongMethodTypeException wmtEx)
   {
      LOGGER.log(
         Level.SEVERE,
         'Unable to invoke method because it is wrong type - {0}',
         wmtEx.toString());
   }
   catch (Throwable throwable)
   {
      LOGGER.log(
         Level.SEVERE,
         'Underlying method threw a Throwable - {0}',
         throwable.toString());
   }
 
   return chart;
}
 
/**
 * Build a MethodHandle for calling the appropriate chart generation method
 * based on the provided ChartTypes choice of chart.
 *
 * @param chartChoice ChartTypes instance indicating which type of chart
 *    is to be generated so that an appropriately named method can be invoked
 *    for generation of that chart.
 * @return MethodHandle for invoking chart generation.
 */
private MethodHandle buildAppropriateMethodHandle(final ChartTypes chartChoice)
{
   MethodHandle methodHandle = null;
   final MethodType methodDescription =
      MethodType.methodType(XYChart.class, Axis.class, Axis.class);
   final String methodName = 'generate' + chartChoice.getChartTypeName() + 'Chart';
 
   try
   {
      methodHandle =
         MethodHandles.lookup().findVirtual(
            this.getClass(), methodName, methodDescription);
   }
   catch (NoSuchMethodException | IllegalAccessException exception)
   {
      LOGGER.log(
         Level.SEVERE,
         'Unable to acquire MethodHandle to method {0} - {1}',
         new Object[]{methodName, exception.toString()});
   }
   return methodHandle;
}

Далее следует серия изображений, показывающая, как эти XY-диаграммы отображаются при визуализации JavaFX.

Диаграмма площади

Гистограмма

Пузырьковая диаграмма

Линия Диаграмма

Точечная диаграмма

Как указывалось выше, дескрипторы методов можно было бы использовать для еще большего сокращения кода, поскольку отдельные методы для генерации каждой XYChart не являются абсолютно необходимыми и могли бы XYChart рефлексии на основе требуемого типа диаграммы. Стоит также подчеркнуть, что если бы данные по оси x были числовыми, код был бы одинаковым (и мог бы быть рефлексивно вызванным) для всех типов XYChart включая Bubble Chart.

JavaFX позволяет легко создавать привлекательные диаграммы, представляющие предоставленные данные. Функции Java 7 делают это еще проще, делая код более кратким и выразительным и позволяя легко применять рефлексию при необходимости.

Ссылка: JavaFX 2 XYCharts и Java 7 Особенности от нашего партнера JCG Дастина Маркса в блоге Inspired by Actual Events .