Как сделать сортировку в матрице по показателю

30.06.2021

Сортировка данных - очень важный инструмент анализа, который позволяет быстро оценить динамику роста или падения, а также просто ранжировать данные для удобства восприятия. Объект матрица в текущей версии FastReport .NET позволяет сортировать только измерения. Например, вы строите отчет выводящий статистику продаж сотрудников по годам. Матрица имеет группировку данных по годам и месяцам. А нам необходимо осуществлять сортировку внутри каждой группы - года. Штатные средства сортировки позволят вам отсортировать фамилии сотрудников, года, месяцы, но не сами данные. Тем более если вы хотите сортировать по определенной колонке.

Выбор порядка сортировки для измерения Name

Чтобы осуществить сортировку по конкретному столбцу построенной матрицы (например, для конкретного сотрудника) придется воспользоваться скриптом отчета. Есть два способа отсортировать построенную матрицу - перемещать строки или ячейки.

На первый взгляд, кажется, что перемещать сразу строки будет более правильным решением, ведь сортировка подразумевает изменение порядка вывода всей строки, а не конкретной ячейки. И это действительно будет самым правильным решением, но не всегда.

Мы рассмотрим случай, когда перемещение строк не сработает. Если ваша матрица имеет группы с подгруппами в измерениях, то возникнут проблемы с перемещением строки, которая идет первой в группе. Такая строка имеет в первой ячейке название группы. Последующие строки из группы имеют пустое значение в первой ячейке. Так как вы во время сортировки может измениться порядок вывода первой строки в группе, то возникнет ошибка, когда на её месте откажется строка с пустым заголовком группы.

Чтобы избежать таких проблем, придется сортировать ячейки в нужном столбце. То есть, вы сначала сортируете нужный столбец, а затем, используя набор индексов ячеек, вы сортируете все остальные колонки в матрице по нему. Очевидно, что этот метод гораздо более трудоёмкий.

Рассмотрим оба случая на примере. Итак, первый - сортировка строк матрицы с помощью удаления и вставки строк в результирующей матрице.

Давайте посмотрим на изначальную матрицу, которую нам нужно отсортировать:

Изначальная матрица

На данном скриншоте представлена простая матрица, которая не имеет групп с подгруппами. Для такого случая идеально подойдет именно перемещение строк. На самом деле мы сначала удалим нужные строки, а затем вставим их в нужном порядке. Всё это будет реализовано с помощью скрипта отчета.

Допустим, нам нужно отсортировать матрицу по столбцу для 2011 года. Нужно определить порядковый номер этого столбца и получить данные для всех его ячеек, за исключением результирующей суммы Total.

Для объекта матрица добавим событие ModifyResult:

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
public class ReportScript
 {
 // ключ пары - значение в ячейке матрицы, значение пары - y-координата ячейки
 // таким образом словарь будет отсортирован по значению ячеек
 private SortedDictionary<double, int> numbers = new SortedDictionary<double, int>();
 
 private void Matrix1_ModifyResult(object sender, EventArgs e)
 {
 int x = 1;
 int y = 2;
 
 // собираем значения ячеек в столбце за 2011 год, будем сортировать по нему
 for ( ; y < Matrix1.ResultTable.RowCount - 1; y++)
 {
 object val = Matrix1.ResultTable.GetCellData(x, y).Value;
 double dval = 0.0;
 if (val != null)
 { 
 // тут важно знать типы значений либо их проверять
 // в этом примере формат ячейки стоит Currency, поэтому его сначала преобразовываем в строку
 // по умолчанию формат ячеек string
 Double.TryParse(val.ToString(), out dval);
 numbers.Add(dval, y);
 }
 // добавляем в словарь пару со значением 0.0 в случае если в ячейке пустая строка
 // в итоге пустые строки будут учитываться при сортировке и будут идти первыми
 else
 {
 numbers.Add(dval, y);
 }
 }
 
 // копируем строки матрицы во вспомогательный массив
 // потом будем из него брать нужные строки и вставлять в матрицу
 object[] originalRows = Matrix1.ResultTable.Rows.ToArray();
 
 int i = 2; // номер строки, с которой начнем удалять строки в матрице
 // теперь удаляем вторую строку в матрице столько раз, сколько строк надо отсортировать
 // все время удаляем вторую строку, так как после ее удаления все строки сместятся вверх на одну позицию
 for (int j = 0; j < numbers.Count; j++)
 {
 Matrix1.ResultTable.Rows.RemoveAt(i);
 }
 
 i = 2; 
 // теперь просто добавляем все строки по порядку согласно отсортированному списку
 foreach (int v in numbers.Values)
 {
 int rowNum = v;
 Matrix1.ResultTable.Rows.Insert(i, originalRows[rowNum] as TableRow);
 i++;
 }
 }
 }

На самом деле суть метода в чтении значений ячеек и их индексов из нужного столбца и записи их в отсортированный словарь. Имея индексы ячеек, а соответственно и строк, мы можем выстроить строки в нужном порядке. Но для этого сначала копируем строки во временный список. Затем удаляем все строки и вставляем их согласно индексам в отсортированном словаре ячеек. Для вставки используем сохраненные во временный список строки матрицы.

В итоге получаем матрицу, отсортированную по столбцу с 2011 годом:

Матрица, отсортированная по столбцу 2011 год

Это простейший пример сквозной сортировки матрицы. А теперь представим, что мы имеем группы для измерений слева и сортировать будем внутри каждой группы. Как уже отмечалось ранее, в этом случае сортировка строк не подходит. Рассмотрим сортировку ячеек.

Перевернем матрицу из предыдущего примера:

Перевернутая матрица

Также, для матрицы создаем обработчик события ModifyResult:

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
public class ReportScript
 {
 
 public class DescendingComparer<T>: IComparer<T> where T : IComparable<T>
 {
 public int Compare(T x, T y)
 {
 return y.CompareTo(x); 
 }
 }
 
 // ключ пары - значение в ячейке матрицы, значение пары - y-координата ячейки
 // таким образом словарь будет отсортирован по значению ячеек
 private SortedList<int, double> numbers = new SortedList<int, double>();
 
 private void Matrix1_ModifyResult(object sender, EventArgs e)
 {
 int x = 3;
 int y = 2;
 
 Dictionary<int, double> cellsMonth = new Dictionary<int, double>();
 Dictionary<int, double> cellsFirst = new Dictionary<int, double>();
 Dictionary<int, double> cellsSecond = new Dictionary<int, double>();
 Dictionary<int, double> cellsThird = new Dictionary<int, double>();
 Dictionary<int, double> cellsFourth = new Dictionary<int, double>();
 Dictionary<int, double> cellsTotal = new Dictionary<int, double>();
 List<List<int>> allCells = new List<List<int>>();
 
 bool other = false;
 int z = 2;
 double val2 = 0.0;
 
 var val3 = 0.0;
 
 string message = "";
 
 List<string> years = new List<string>();
 
 for (int j=0; j<Matrix1.ResultTable.RowCount; j++)
 {
 var column = Matrix1.ResultTable.Columns[0] as TableColumn;
 try
 {
 years.Add(Matrix1.ResultTable.GetCellData(0,j).Value.ToString());
 }
 catch (Exception)
 {}
 } 
 
 //Для каждого года прогоняем цикл
 foreach (var year in years)
 {
 total = false;
 //Получаем значения ячеек для каждого года по заданному столбцу
 while (!total)
 {
 //Исключаем попадание Total в список сортируемых значений
 if (Matrix1.ResultTable.GetCellData(1,z).Text!="Total")
 {
 //Столбец месяцев
 var value = Matrix1.ResultTable.GetCellData(1,z).Value;
 if (value!=null)
 {
 Double.TryParse(value.ToString(),out val3);
 cellsMonth.Add(z,val3);
 }
 else
 cellsMonth.Add(z, 0.0);
 
 //Столбец для первого сотрудника
 value = Matrix1.ResultTable.GetCellData(2,z).Value;
 if (value!=null)
 {
 Double.TryParse(value.ToString(),out val3);
 cellsFirst.Add(z,val3);
 }
 else
 cellsFirst.Add(z, 0.0);
 
 //Столбец для второго сотрудника
 value = Matrix1.ResultTable.GetCellData(3,z).Value;
 if (value!=null)
 {
 Double.TryParse(value.ToString(),out val3);
 cellsSecond.Add(z,val3);
 }
 else
 cellsSecond.Add(z, 0.0);
 
 //Столбец для третьего сотрудника
 value = Matrix1.ResultTable.GetCellData(5,z).Value;
 if (value!=null)
 {
 Double.TryParse(value.ToString(),out val3);
 cellsFourth.Add(z,val3);
 }
 else
 cellsFourth.Add(z, 0.0);
 
 //Сортируемый столбец. Будет служить эталоном сортировки для всех остальных
 value = Matrix1.ResultTable.GetCellData(4,z).Value;
 if (value!=null)
 {
 Double.TryParse(value.ToString(),out val3);
 cellsThird.Add(z,val3);
 }
 else
 cellsThird.Add(z, 0.0);
 
 //Столбец для пятого сотрудника
 value = Matrix1.ResultTable.GetCellData(6,z).Value;
 if (value!=null)
 {
 Double.TryParse(value.ToString(),out val3);
 cellsTotal.Add(z,val3);
 }
 else
 cellsTotal.Add(z, 0.0);
 }
 else
 {
 total = true;
 } 
 z++;
 } 
 
 //Сортируем по списку cellsThird - колонка для третьего сотрудника
 var keys = cellsThird.OrderByDescending(i=>i.Value).Select(key => key.Key).ToList();
 
 //Задаем новое значение для ячеек во всех строках в необходимых колонках согласно порядку в отсортированном словаре для третьего столбца
 int k = 0;
 foreach(var key in keys)
 {
 Matrix1.ResultTable.GetCellData(1, cellsThird.Keys.ElementAt(k)).Text = cellsMonth[key].ToString();
 Matrix1.ResultTable.GetCellData(2, cellsThird.Keys.ElementAt(k)).Text = cellsFirst[key].ToString();
 Matrix1.ResultTable.GetCellData(3, cellsThird.Keys.ElementAt(k)).Text = cellsSecond[key].ToString();
 Matrix1.ResultTable.GetCellData(4, cellsThird.Keys.ElementAt(k)).Text = cellsThird[key].ToString();
 Matrix1.ResultTable.GetCellData(5, cellsThird.Keys.ElementAt(k)).Text = cellsFourth[key].ToString();
 Matrix1.ResultTable.GetCellData(6, cellsThird.Keys.ElementAt(k)).Text = cellsTotal[key].ToString();
 k++;
 }
 cellsThird.Clear();
 }
 }
 }

Принципиальных отличий от предыдущего способа два - сортируем с помощью замещения, а не удаления/вставки и сортируем по каждой колонке отдельно.

По комментариям в коде должно быть понятно, что и где происходит. Но все же давайте кратко рассмотрим:

1) Прежде всего мы получаем значения групп измерений, чтобы знать, сколько наборов сортировки нам потребуется. Для каждой группы свой порядок сортировки.
2) Далее, получаем данные по всем нужным для сортировки столбцам. Это значит - по всем столбцам, кроме первого, в котором содержатся названия групп измерений.
3) Затем выбираем нужный для сортировки набор значений и сортируем его.
4) Используя полученный словарь, в котором ключ - это индекс ячейки, можно выстроить ячейки во всех сортируемых столбцах согласно порядку этих индексов.

В результате получается отсортированная для Nancy Davolio матрица:

Матрица, отсортированная по столбцу для сотрудника Nancy Davolio

Таким образом, можно отсортировать матрицу по любому столбцу данных. Более того, сделать кастомную сортировку - не только по убыванию или возрастанию. Дополнительно можно исключать из сортировки определенные строки (Total или вычисляемые) задав им индивидуальный порядок расположения. 

20 ноября 2024

Локализация и смена языков в FastReport VCL

FastReport VCL поддерживает 40 языков для локализации интерфейса и позволяет изменять язык на лету через меню или код, без перекомпиляции.
1 ноября 2024

Новые возможности редактора отчетов FastReport VCL

Рассматриваем новые возможности редактора отчетов: выносные линии, подсветка пересекающихся объектов, обновлённые деревья отчетов и данных.
30 октября 2024

Использование стилей при создании отчетов в FastReport VCL

В статье подробно рассматривается одна из новых возможностей FastReport VCL – применение стилей и страниц стилей.