Сортировка данных - очень важный инструмент анализа, который позволяет быстро оценить динамику роста или падения, а также просто ранжировать данные для удобства восприятия. Объект матрица в текущей версии FastReport .NET позволяет сортировать только измерения. Например, вы строите отчет выводящий статистику продаж сотрудников по годам. Матрица имеет группировку данных по годам и месяцам. А нам необходимо осуществлять сортировку внутри каждой группы - года. Штатные средства сортировки позволят вам отсортировать фамилии сотрудников, года, месяцы, но не сами данные. Тем более если вы хотите сортировать по определенной колонке.
Чтобы осуществить сортировку по конкретному столбцу построенной матрицы (например, для конкретного сотрудника) придется воспользоваться скриптом отчета. Есть два способа отсортировать построенную матрицу - перемещать строки или ячейки.
На первый взгляд, кажется, что перемещать сразу строки будет более правильным решением, ведь сортировка подразумевает изменение порядка вывода всей строки, а не конкретной ячейки. И это действительно будет самым правильным решением, но не всегда.
Мы рассмотрим случай, когда перемещение строк не сработает. Если ваша матрица имеет группы с подгруппами в измерениях, то возникнут проблемы с перемещением строки, которая идет первой в группе. Такая строка имеет в первой ячейке название группы. Последующие строки из группы имеют пустое значение в первой ячейке. Так как вы во время сортировки может измениться порядок вывода первой строки в группе, то возникнет ошибка, когда на её месте откажется строка с пустым заголовком группы.
Чтобы избежать таких проблем, придется сортировать ячейки в нужном столбце. То есть, вы сначала сортируете нужный столбец, а затем, используя набор индексов ячеек, вы сортируете все остальные колонки в матрице по нему. Очевидно, что этот метод гораздо более трудоёмкий.
Рассмотрим оба случая на примере. Итак, первый - сортировка строк матрицы с помощью удаления и вставки строк в результирующей матрице.
Давайте посмотрим на изначальную матрицу, которую нам нужно отсортировать:
На данном скриншоте представлена простая матрица, которая не имеет групп с подгруппами. Для такого случая идеально подойдет именно перемещение строк. На самом деле мы сначала удалим нужные строки, а затем вставим их в нужном порядке. Всё это будет реализовано с помощью скрипта отчета.
Допустим, нам нужно отсортировать матрицу по столбцу для 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 годом:
Это простейший пример сквозной сортировки матрицы. А теперь представим, что мы имеем группы для измерений слева и сортировать будем внутри каждой группы. Как уже отмечалось ранее, в этом случае сортировка строк не подходит. Рассмотрим сортировку ячеек.
Перевернем матрицу из предыдущего примера:
Также, для матрицы создаем обработчик события 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 матрица:
Таким образом, можно отсортировать матрицу по любому столбцу данных. Более того, сделать кастомную сортировку - не только по убыванию или возрастанию. Дополнительно можно исключать из сортировки определенные строки (Total или вычисляемые) задав им индивидуальный порядок расположения.