private static void WriteEquipmentData(ResultWorkSheet currWorkSheet, ISimulationResult simulationResult)
        {
            //запишем данные по загрузке оборудования
            int startRow         = currWorkSheet.currRow;
            int stopRow          = 1;
            int currEquipmentNum = 0;

            foreach (IEquipment eq in simulationResult.TaskByEquipment.Keys)
            {
                int startColumn = currEquipmentNum * currWorkSheet.columnsPerTask;
                //добавим заголовок
                int headerHeight = AddTaskHeader(eq, startRow, startColumn, currWorkSheet);

                int taskNum = 0;
                //заполним данные по всем задачам для этого оборудования
                foreach (var task in simulationResult.TaskByEquipment[eq])
                {
                    currWorkSheet.AdvanceToRow(startRow + taskNum + headerHeight);
                    currWorkSheet.AdvanceToColumn(startColumn);
                    if (currWorkSheet.currRow > stopRow)
                    {
                        stopRow = currWorkSheet.currRow;
                    }
                    currWorkSheet.Write(string.Format("{0}", task.id), WriteOptions.SingleCell);
                    currWorkSheet.Write(string.Format("{0}", task.description), WriteOptions.SingleCell);
                    currWorkSheet.Write(string.Format("{0}", task.startedAt), WriteOptions.SingleCell);
                    currWorkSheet.Write(string.Format("{0}", task.duration), WriteOptions.SingleCell);
                    taskNum++;
                }
                currWorkSheet.NewLine();
                currWorkSheet.AdvanceToColumn(startColumn + 3);
                currWorkSheet.Write(string.Format("sum(indirect(address({0},{2})&\":\"&address({1},{2})))",
                                                  startRow + headerHeight, currWorkSheet.currRow - 1,
                                                  currWorkSheet.currColumn), WriteOptions.Formula);
                currEquipmentNum++;
            }

            //перейдем на последнюю строку, которую заполнили по оборудованию
            stopRow++; //учитываем формулу "всего"
            currWorkSheet.AdvanceToRow(stopRow);

            //для таблички оборудования сделаем красивую автоширину колонки
            //AutoFitColumns() не работает нигде и никак
            //currWorkSheet.worksheet.Cells.AutoFitColumns();

            //уменьшим шрифт таблицы
            currWorkSheet.worksheet.Cells[startRow, 1,
                                          currWorkSheet.currRow, currWorkSheet.worksheet.Dimension.End.Column]
            .Style.Font.Size = 8.0f;
            //вернем красивость заголовку
            currWorkSheet.ApplyStyle(currWorkSheet.worksheet.Row(startRow).Style, WriteStyle.BigHeader);
            currWorkSheet.NewLine();
        }
        /// <summary>
        /// записать результаты моделирования в файл Excel
        /// </summary>
        public static void ToExcel(this ISimulationResult simulationResult, string filename)
        {
            if (File.Exists(filename))  //всегда создаем новый файл результатов
            {
                File.Delete(filename);
            }

            FileInfo excelFile = new FileInfo(filename);

            //в пути могут быть пока еще не созданные каталоги, создадим их
            Directory.CreateDirectory(excelFile.DirectoryName);

            using (ExcelPackage package = new ExcelPackage(excelFile)) {
                ResultWorkSheet currWorkSheet = new ResultWorkSheet();
                //"План загрузки оборудования" - слишком много для названия листа :=)
                currWorkSheet.worksheet = package.Workbook.Worksheets.Add("План загрузки");

                currWorkSheet.currRow = 1;

                if (simulationResult.HasErrors)  //были ошибки, просто вернем их список
                {
                    WriteErrors(currWorkSheet, simulationResult.Errors);
                    package.Save();
                    return;
                }
                //запишем данные по оборудованию
                currWorkSheet.startingColumn   = 2; //для красивости немного отступим
                currWorkSheet.singleColumnSize = 1;
                WriteEquipmentData(currWorkSheet, simulationResult);
                currWorkSheet.startingColumn = 1;
                currWorkSheet.NewLine();

                //запишем  метрики
                currWorkSheet.singleColumnSize = 4; //для метрик нужно побольше места
                WriteMetrics(currWorkSheet, simulationResult.Metrics);
                currWorkSheet.NewLine();

                //запишем все примечания
                foreach (string str in simulationResult.Footnotes)
                {
                    currWorkSheet.Write(str, WriteOptions.FullWidth);
                }
                package.Save();
            }
        }
 private static void WriteMetrics(ResultWorkSheet currWorkSheet, IDictionary <string, string> metrics)
 {
     //запишем все метрики
     foreach (var pair in metrics)
     {
         //метрика - это строка названия + строка значения
         //запишем в соседних ячейках
         currWorkSheet.Write(pair.Key, WriteOptions.SingleCell);
         currWorkSheet.Write(pair.Value, WriteOptions.SingleCell);
         currWorkSheet.NewLine();
     }
 }
        /// <summary>
        /// добавляет заголовок для оборудования
        /// Возвращает высоту заголовка в строках
        /// </summary>
        private static int AddTaskHeader(IEquipment eq, int startRow, int startColumn, ResultWorkSheet currWorkSheet)
        {
            IDictionary <int, double> preferredWith = new Dictionary <int, double>()
            {
                { 0, 6 },  //"id задачи"
                { 1, 10 }, //"задача"
                { 2, 8 },  //"время начала"
                { 3, 8 } //"продолжительность"
            };

            //перейдем в нужное место
            currWorkSheet.AdvanceToRow(startRow);
            currWorkSheet.AdvanceToColumn(startColumn);
            int headerFirstColumn = currWorkSheet.currColumn;

            currWorkSheet.Write(string.Format("{0} id={1}", eq.name, eq.id), WriteOptions.SingleCell, WriteStyle.BigHeader);
            currWorkSheet.AdvanceToColumn(startColumn + preferredWith.Keys.Count - 1);
            int headerLastColumn = currWorkSheet.currColumn;

            currWorkSheet.worksheet.Cells[startRow, headerFirstColumn, startRow, headerLastColumn].Merge = true;

            //cоздаем подзаголовки для задачи
            currWorkSheet.NewLine();
            currWorkSheet.AdvanceToColumn(startColumn);

            //выпишем заголовок для задачи
            //сразу задаем нужную ширину; автоматическое проставление по содержимому не получилось
            for (int j = 0; j < preferredWith.Count; j++)
            {
                currWorkSheet.worksheet.Column(currWorkSheet.currColumn + j).Width = preferredWith[j];
            }
            //для подзаголовка нужна высота побольше
            currWorkSheet.worksheet.Row(currWorkSheet.currRow).Height = 32;

            //cобственно подзаголовки
            currWorkSheet.Write("id задачи", WriteOptions.SingleCell, WriteStyle.Header);
            currWorkSheet.Write("задача", WriteOptions.SingleCell, WriteStyle.Header);
            currWorkSheet.Write("время начала", WriteOptions.SingleCell, WriteStyle.Header);
            currWorkSheet.Write("продолжительность", WriteOptions.SingleCell, WriteStyle.Header);

            return(2);
        }