/// <summary>
        /// Processes the dynamic chart.
        /// </summary>
        /// <param name="tableDataSheetName">Name of the table data sheet.</param>
        /// <param name="tableData">The table data.</param>
        /// <param name="chartModel">The chart model.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException">chartModel</exception>
        private static bool ProcessDynamicChart(string tableDataSheetName, TableData tableData, ChartModel chartModel)
        {
            if (chartModel == null)
            {
                throw new ArgumentNullException("chartModel");
            }

            TempDiagnostics.Output(string.Format("Processing chart in sheet '{0}'", tableDataSheetName));

            var seriesFactory = new SeriesFactory(chartModel);

            if (seriesFactory.SourceSeriesCount > 0)
            {
                if (tableData.TreatRowAsSeries == false)
                {
                    // A COLUMN of data represents a series...
                    // Find the Range that representes the first Axis Data for all of the series...
                    CompositeRangeReference category1AxisRange = DetermineCategory1AxisRange(tableData, tableData.TreatRowAsSeries, tableDataSheetName);

                    foreach (TableColumn column in tableData.Columns)
                    {
                        // Skip if we are on the category column
                        if (column.DataRegion.ExcelColumnStart == category1AxisRange.MinColumnIndex)
                        {
                            continue;
                        }

                        // Get information about the series that will be based on the column
                        var seriesInfo = new ChartSeriesInfo(column);

                        // We need a column that has not been actively excluded
                        if (seriesInfo.BaseOnChartSeriesIndex >= 0 && !seriesInfo.SuppressSeries)
                        {
                            // Get the template series or get a copy if already used
                            OpenXmlCompositeElement clonedSeries = seriesFactory.GetOrCloneSourceSeries(seriesInfo.BaseOnChartSeriesIndex);

                            //TODO: Allow ability to assign colour palettes (as opposed to colours) to dynamically generated series
                            Color?seriesColour = seriesInfo.SeriesColour;
                            if (!seriesColour.HasValue)
                            {
                                int useCount = seriesFactory.GetUseCount(seriesInfo.BaseOnChartSeriesIndex);
                                seriesColour = new Color?((ColourPalette.GetColour(ColourPaletteType.GamTechnicalChartPalette, useCount - 1)));
                            }

                            var seriesTextRange = new CompositeRangeReference(new RangeReference(tableDataSheetName,
                                                                                                 (uint)column.DataRegion.ExcelRowStart,
                                                                                                 (uint)column.DataRegion.ExcelColumnStart));



                            // Series data is first column only
                            var seriesValuesRange = new CompositeRangeReference(new RangeReference(tableDataSheetName,
                                                                                                   (uint)column.DataRegion.ExcelRowStart + 1,
                                                                                                   (uint)column.DataRegion.ExcelColumnStart,
                                                                                                   (uint)column.DataRegion.ExcelRowEnd,
                                                                                                   (uint)column.DataRegion.ExcelColumnStart));

                            // Determine data ranges to be used within chart series.
                            ChartDataRangeInfo dataRangeInfo = new ChartDataRangeInfo
                            {
                                SeriesTextRange       = seriesTextRange,
                                CategoryAxisDataRange = category1AxisRange,
                                SeriesValuesRange     = seriesValuesRange,
                            };

                            // update all formula on the series
                            UpdateChartSeriesDataReferences(clonedSeries, dataRangeInfo, new SolidColorBrush(seriesColour.Value));
                        }
                    }
                }
                else
                {
                    // A ROW of data represents a series...
                    // Find the Range that representes the first Axis Data for all of the series...
                    CompositeRangeReference category1AxisRange = DetermineCategory1AxisRange(tableData, tableData.TreatRowAsSeries, tableDataSheetName);
                    TableColumn             seriesTextColumn   = tableData.Columns[FindNonExcludedColumnIndex(tableData, 1)];

                    // Build a list of columns to include in the series
                    var seriesTableColumns = new List <TableColumn>();

                    int         seriesValuesColumnIndex = FindNonExcludedColumnIndex(tableData, 2);
                    TableColumn firstSeriesValuesColumn = tableData.Columns[seriesValuesColumnIndex];
                    seriesTableColumns.Add(firstSeriesValuesColumn);

                    // Count up to last column, excluding those marked for exclusion
                    while (seriesValuesColumnIndex < (tableData.Columns.Count - 1))
                    {
                        seriesValuesColumnIndex++;
                        TableColumn tableColumn = tableData.Columns[seriesValuesColumnIndex];

                        ChartExcludeOption excludeOption = tableColumn.ChartOptions.GetOptionOrDefault <ChartExcludeOption>();
                        if (excludeOption == null || excludeOption.Exclude == false)
                        {
                            seriesTableColumns.Add(tableColumn);
                        }
                    }

                    foreach (TableDataRowInfo rowInfo in tableData.RowData)
                    {
                        // Extract chart series related properties from row data
                        var seriesInfo = new ChartSeriesInfo(rowInfo.RowData);

                        // Determine where data has been written into Excel
                        uint rowIndex = (uint)tableData.MapContainer.ExcelRowStart + rowInfo.TableRowIndex - 1;

                        // Skip if we are on the category column
                        if (rowIndex == category1AxisRange.MinRowIndex)
                        {
                            continue;
                        }

                        // We need a column that has not been actively excluded
                        if (seriesInfo.BaseOnChartSeriesIndex >= 0 && !seriesInfo.SuppressSeries)
                        {
                            // Get the template series or get a copy if already used
                            OpenXmlCompositeElement clonedSeries = seriesFactory.GetOrCloneSourceSeries(seriesInfo.BaseOnChartSeriesIndex);

                            //TODO: Allow ability to assign colour palettes (as opposed to colours) to dynamically generated series
                            Color?seriesColour = seriesInfo.SeriesColour;
                            if (!seriesColour.HasValue)
                            {
                                int useCount = seriesFactory.GetUseCount(seriesInfo.BaseOnChartSeriesIndex);
                                seriesColour = new Color?((ColourPalette.GetColour(ColourPaletteType.GamTechnicalChartPalette, useCount - 1)));
                            }

                            // SeriesText is the series heading used in legends
                            var seriesTextRange = new CompositeRangeReference(new RangeReference(tableDataSheetName,
                                                                                                 rowIndex,
                                                                                                 (uint)seriesTextColumn.DataRegion.ExcelColumnStart));

                            // From column after category to last column in table.
                            var seriesValuesRange = new CompositeRangeReference();
                            foreach (TableColumn tableColumn in seriesTableColumns)
                            {
                                seriesValuesRange.Update(tableDataSheetName,
                                                         rowIndex,
                                                         (uint)tableColumn.DataRegion.ExcelColumnStart,
                                                         rowIndex,
                                                         (uint)tableColumn.DataRegion.ExcelColumnStart);
                            }

                            // Determine data ranges to be used within chart series.
                            ChartDataRangeInfo dataRangeInfo = new ChartDataRangeInfo
                            {
                                SeriesTextRange       = seriesTextRange,
                                CategoryAxisDataRange = category1AxisRange,
                                SeriesValuesRange     = seriesValuesRange,
                            };

                            // update all formula on the series ( I know we're constantly swapping between brush and color here.... Address later)
                            UpdateChartSeriesDataReferences(clonedSeries, dataRangeInfo, new SolidColorBrush(seriesColour.Value));
                        }
                    }
                }

                // Remove all un-used template series and set the order of remaining.
                uint seriesIndex = 0;
                for (int idx = 0; idx < seriesFactory.SourceSeriesCount; idx++)
                {
                    OpenXmlCompositeElement templateSeries = seriesFactory.GetSourceSeriesElement(idx);

                    // Value is the use-count, if zero then the template needs removing.
                    if (seriesFactory.GetUseCount(idx) == 0)
                    {
                        // Remove the template series.
                        templateSeries.Remove();
                    }
                    else
                    {
                        // Set template series index and order to initial value, then set clones
                        var templateIndex = templateSeries.Descendants <DrawingCharts.Index>().FirstOrDefault();
                        if (templateIndex != null)
                        {
                            templateIndex.Val = seriesIndex;
                        }

                        var templateOrder = templateSeries.Descendants <DrawingCharts.Order>().FirstOrDefault();
                        if (templateOrder != null)
                        {
                            templateOrder.Val = seriesIndex;
                        }

                        seriesIndex++;

                        OpenXmlElement lastElement = templateSeries;

                        foreach (var clonedElement in seriesFactory.GetClonedSeriesElements(idx))
                        {
                            var index = clonedElement.Descendants <DrawingCharts.Index>().FirstOrDefault();
                            if (index != null)
                            {
                                index.Val = seriesIndex;
                            }

                            var order = clonedElement.Descendants <DrawingCharts.Order>().FirstOrDefault();
                            if (order != null)
                            {
                                order.Val = seriesIndex;
                            }

                            lastElement.InsertAfterSelf <OpenXmlElement>(clonedElement);
                            lastElement = clonedElement;
                            seriesIndex++;
                        }
                    }
                }
            }

            return(true);
        }
Example #2
0
        /// <summary>
        /// Write the content of the <see cref="ExcelMapCoOrdinateContainer"> to the worksheet</see>.
        /// </summary>
        /// <param name="sheetName">Name of the sheet.</param>
        /// <param name="worksheetPart">The worksheet part.</param>
        /// <param name="mapCoOrdinateContainer">The map co ordinate container.</param>
        /// <param name="stylesManager">The styles manager.</param>
        /// <param name="spreadsheetDocument">The spreadsheet document.</param>
        public static void WriteMapToExcel(string sheetName,
                                           WorksheetPart worksheetPart,
                                           ExcelMapCoOrdinateContainer mapCoOrdinateContainer,
                                           ExcelStylesManager stylesManager,
                                           SpreadsheetDocument spreadsheetDocument)
        {
            TempDiagnostics.Output(string.Format("Writing map for ExcelMap[{0}] to worksheet '{1}'", mapCoOrdinateContainer.ContainerType, sheetName));

            var dimensionConverter = new ExcelDimensionConverter("Calibri", 11.0f);

            // Manages the writing to Excel.
            var excelWriteManager = new OpenXmlExcelWriteManager(worksheetPart.Worksheet);

            //** First we need to clear the destination worksheet down (rows, columns and merged areas)
            excelWriteManager.EmptyWorksheet();

            // Build up Columns models on all nested containers.
            RowOrColumnsModel columnsModel = mapCoOrdinateContainer.BuildColumnsModel();
            int colCount = columnsModel.Count();

            // ==========================================================
            // Traverse each column assigning start and end
            // column indexes to the maps associated with that column.
            // ==========================================================
            RowOrColumnInfo columnInfo = columnsModel.First;

            while (columnInfo != null)
            {
                double?width = columnInfo.HeightOrWidth.HasValue ? (double?)dimensionConverter.WidthToOpenXmlWidth(columnInfo.HeightOrWidth.Value) : null;
                excelWriteManager.AddColumn(width, columnInfo.Hidden);

                // Assign Excel start and end columns to maps for merge operations.
                foreach (ExcelMapCoOrdinate map in columnInfo.Maps)
                {
                    // Extend any cells that are merged across maps.
                    if (map is ExcelMapCoOrdinatePlaceholder)
                    {
                        var cell = map as ExcelMapCoOrdinatePlaceholder;
                        if (cell.MergeWith != null)
                        {
                            cell.ExcelColumnStart         = cell.MergeWith.ExcelColumnStart;
                            cell.MergeWith.ExcelColumnEnd = columnInfo.ExcelIndex; //excelCol
                        }
                    }

                    if (map.ExcelColumnStart == 0)
                    {
                        map.ExcelColumnStart = columnInfo.ExcelIndex; //excelCol;
                        map.ExcelColumnEnd   = columnInfo.ExcelIndex; //excelCol;
                    }
                    else
                    {
                        map.ExcelColumnEnd = columnInfo.ExcelIndex; //excelCol;
                    }
                }

                // Move on to next column in list
                columnInfo = columnInfo.Next;
            }
            TempDiagnostics.Output(string.Format("Created ColumnsModel which contains '{0}' columns", colCount));

            // Build up Rows models on all nested containers.
            RowOrColumnsModel rowsModel = mapCoOrdinateContainer.BuildRowsModel();
            int rowCount = rowsModel.Count();

            // ==========================================================
            // Traverse each row assigning start and end
            // row indexes to the maps associated with that row.
            // ==========================================================
            RowOrColumnInfo rowInfo = rowsModel.First;

            while (rowInfo != null)
            {
                double?height = rowInfo.HeightOrWidth.HasValue ? (double?)dimensionConverter.HeightToOpenXmlHeight(rowInfo.HeightOrWidth.Value) : null;
                excelWriteManager.AddRow(height, rowInfo.Hidden);

                // Assign Excel start and end rows to maps for merge operations.
                foreach (ExcelMapCoOrdinate map in rowInfo.Maps)
                {
                    if (map.ExcelRowStart == 0)
                    {
                        map.ExcelRowStart = rowInfo.ExcelIndex; //excelRow;
                        map.ExcelRowEnd   = rowInfo.ExcelIndex; //excelRow;
                    }
                    else
                    {
                        map.ExcelRowEnd = rowInfo.ExcelIndex; //excelRow;
                    }
                }

                // Move on to next Row in list
                rowInfo = rowInfo.Next;
            }
            TempDiagnostics.Output(string.Format("Created RowsModel which contains '{0}' rows", rowCount));

            // Build a layered cells dictionary for all cells, keyed by Excel row and column index
            var layeredCellsDictionary = new LayeredCellsDictionary();

            mapCoOrdinateContainer.UpdateLayeredCells(ref layeredCellsDictionary);
            TempDiagnostics.Output(string.Format("Updated Layered Cell Information for worksheet '{0}' = {1}", sheetName, layeredCellsDictionary.Count));

            // Probe the Row, Column and Cell Layered maps, embellishing them with row, column and cell formatting information.
            for (uint worksheetRow = 1; worksheetRow <= rowCount; worksheetRow++)
            {
                for (uint worksheetCol = 1; worksheetCol <= colCount; worksheetCol++)
                {
                    // We can now use the layeredCellsDictionary to build the
                    // excel workbook based on layered cell information
                    var             currentCoOrdinate = new System.Drawing.Point((int)worksheetCol, (int)worksheetRow);
                    LayeredCellInfo layeredCellInfo   = layeredCellsDictionary[currentCoOrdinate];

                    // Work through the layered maps to determine what needs to be written to the Excel worksheet at that Row/Column
                    ProcessLayeredCellMaps(currentCoOrdinate, layeredCellInfo);
                }
            }
            TempDiagnostics.Output(string.Format("Built Worksheet CellInfos[Cols={0},Rows={1}]", colCount, rowCount));

            //** Write the 2D array of cell information to the Excel Worksheet
            //** building a list of areas that are to be merged.
            for (uint worksheetRow = 1; worksheetRow <= rowCount; worksheetRow++)
            {
                OpenXmlSpreadsheet.Row row = excelWriteManager.GetRow(worksheetRow);

                for (uint worksheetCol = 1; worksheetCol <= colCount; worksheetCol++)
                {
                    // Pluck out information relating to the current cell
                    var           currentCoOrdinate = new System.Drawing.Point((int)worksheetCol, (int)worksheetRow);
                    ExcelCellInfo cellInfo          = layeredCellsDictionary[currentCoOrdinate].CellInfo;

                    // Not sure if we need this as column letters are easily (quickly) translated using existing OpenXml helpers
                    string columnLetter = excelWriteManager.GetColumnLetter(worksheetCol);

                    // All merge cells should have the same style as the source merge cell.
                    if (cellInfo.MergeFrom != null)
                    {
                        // If MergeFrom is non-null, then this cell has been already been marked as a merge cell,
                        // whose style is to be the same as the source 'MergeFrom cell.
                        // Update the source (MergeFrom) cell so it ends up containing a reference to the last (MergeTo) cell to be merged.
                        cellInfo.MergeFrom.MergeTo = cellInfo;

                        // Create/lookup styles in the target workbook and return index of created style
                        uint cellStyleIndex = stylesManager.GetOrCreateStyle(cellInfo.MergeFrom.StyleInfo);

                        // Write in to Excel (style information only)
                        var cell = new OpenXmlSpreadsheet.Cell();
                        cell.SetCellReference(columnLetter, worksheetRow);
                        cell.StyleIndex = cellStyleIndex;
                        row.Append(cell);
                        cellInfo.Cell = cell;
                    }
                    else
                    {
                        // Create/lookup styles in the target workbook and return index of created style
                        uint cellStyleIndex = stylesManager.GetOrCreateStyle(cellInfo.StyleInfo);

                        // NB! If we write a Null value then we lose cell formatting information for some reason
                        object value = cellInfo.Value == null ? string.Empty : cellInfo.Value;

                        // Write in to Excel
                        var cell = new OpenXmlSpreadsheet.Cell();
                        cell.SetCellReference(columnLetter, worksheetRow);
                        excelWriteManager.SetCellValue(cell, value);
                        cell.StyleIndex = cellStyleIndex;
                        row.Append(cell);
                        cellInfo.Cell = cell;
                    }

                    // Span the cell accross it's parent columns if specified
                    if (cellInfo.LastSpanRow == 0)
                    {
                        cellInfo.LastSpanRow = worksheetRow;
                    }
                    if (cellInfo.LastSpanColumn == 0)
                    {
                        cellInfo.LastSpanColumn = worksheetCol;
                    }

                    // Merge cells if required (spanning)
                    for (uint rowIdx = worksheetRow; rowIdx <= cellInfo.LastSpanRow; rowIdx++)
                    {
                        for (uint colIdx = worksheetCol; colIdx <= cellInfo.LastSpanColumn; colIdx++)
                        {
                            // Mark processed (so we don't over-write)
                            var coOrdinate = new System.Drawing.Point((int)colIdx, (int)rowIdx);
                            if (coOrdinate != currentCoOrdinate)
                            {
                                var           mergeCoOrdinate = new System.Drawing.Point((int)colIdx, (int)rowIdx);
                                ExcelCellInfo mergeCellInfo   = layeredCellsDictionary[mergeCoOrdinate].CellInfo;
                                if (mergeCellInfo.MergeFrom == null)
                                {
                                    mergeCellInfo.MergeFrom = cellInfo;
                                }
                            }
                        }
                    }
                }
            }

//#if DEBUG
//            if (!skipMerge)
//            {
//#endif
            // Merge cells that have been marked to merge in the cellInfos dictionary.
            MergeCells(worksheetPart.Worksheet, (uint)rowCount, (uint)colCount, layeredCellsDictionary);
//#if DEBUG
//            }
//#endif
            TempDiagnostics.Output(string.Format("Written CellInfos[Cols={0},Rows={1}] to Excel", colCount, rowCount));

            // Create a list of all of the the defined names in the container.
            var definedNameList = new List <ExcelDefinedNameInfo>();

            mapCoOrdinateContainer.UpdateDefinedNameList(ref definedNameList, sheetName);

            // And write into Excel workbook
            foreach (var definedNameInfo in definedNameList)
            {
                uint rangeColumnCount = definedNameInfo.EndColumnIndex - definedNameInfo.StartColumnIndex + 1;
                uint rangeRowCount    = definedNameInfo.EndRowIndex - definedNameInfo.StartRowIndex + 1;

                if (rangeRowCount > 0 && rangeColumnCount > 0)
                {
                    spreadsheetDocument.WorkbookPart.Workbook.AddDefinedName(sheetName,
                                                                             definedNameInfo.DefinedName,
                                                                             (uint)definedNameInfo.StartColumnIndex,
                                                                             (uint)definedNameInfo.StartRowIndex,
                                                                             (int)rangeColumnCount,
                                                                             (int)rangeRowCount);
                }
            }

            TempDiagnostics.Output(string.Format("Defined Names added - write map for {0} complete...", mapCoOrdinateContainer.ContainerType));
        }