// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // Purpose: Chart Legend consist of default and custom legend // items. Default items are automatically added based // on the data series and custom items are added by // the user. Each item usually consist of 2 cells; // series color marker and series name. Legend item // cells form vertical columns in the legend. // Please refer to the Chart documentation which // contains images and samples describing legend features. // : // NOTE: In early versions of the Chart control only 1 legend was // exposed through the Legend property of the root chart object. // Due to the customer requests, support for unlimited number of // legends was added through the LegendCollection exposed as a // Legends property in the root chart object. Old propertys was // deprecated and marked as non-browsable. // using System; using System.Windows.Forms; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Design; using System.Drawing.Drawing2D; using FastReport.DataVisualization.Charting.ChartTypes; using FastReport.DataVisualization.Charting.Utilities; namespace FastReport.DataVisualization.Charting { using Size = System.Drawing.Size; #region Legend enumerations /// /// An enumeration of legend item orderings. /// public enum LegendItemOrder { /// /// Items will be added into the legend in an order automatically determined by the chart. /// Auto, /// /// Items will be added into the legend in the same order as the chart series. /// SameAsSeriesOrder, /// /// Items will be added into the legend in the same order as the chart series. /// ReversedSeriesOrder }; /// /// An enumeration of legend separator styles. /// public enum LegendSeparatorStyle { /// /// No separator will be shown. /// None, /// /// Single solid line separator. /// Line, /// /// Single solid thick line separator. /// ThickLine, /// /// Double solid line separator. /// DoubleLine, /// /// Single dash line separator. /// DashLine, /// /// Single dot line separator. /// DotLine, /// /// Gradient solid line separator. /// GradientLine, /// /// Thick gradient solid line separator. /// ThickGradientLine, } /// /// An enumeration that specifies a style for a legend item's symbol. /// public enum LegendImageStyle { /// /// The symbol will be a rectangle. /// Rectangle, /// /// The symbol will be a line. /// Line, /// /// The symbol will be a marker. /// Marker } /// /// An enumeration of legend styles. /// public enum LegendStyle { /// /// One column, many rows. /// Column, /// /// One row, many columns. /// Row, /// /// Many column, many rows. /// Table }; /// /// An enumeration of legend table styles. /// public enum LegendTableStyle { /// /// The legend table style is automatically determined by the chart. /// Auto, /// /// The legend items will be fit horizontally within the legend. /// It is preferred to use this style when the docking is set to top or bottom. /// Wide, /// /// The legend items will be fit vertically within the legend. /// It is preferred to use this style when docking is set to left or right. /// Tall }; #endregion /// /// The legend class represents a single chart legend. It contains visual /// appearance, position and content properties. This class is also /// responsible for drawing and positioning of the legend. /// [ SRDescription("DescriptionAttributeLegend_Legend"), DefaultProperty("Enabled"), ] public class Legend : ChartNamedElement { #region Fields //*********************************************************** //** Private data members, which store properties values //*********************************************************** private ElementPosition _position = null; private bool _enabled = true; private LegendStyle _legendStyle = LegendStyle.Table; private LegendTableStyle _legendTableStyle = LegendTableStyle.Auto; private LegendItemsCollection _customLegends = null; private ChartHatchStyle _backHatchStyle = ChartHatchStyle.None; private string _backImage = ""; private ChartImageWrapMode _backImageWrapMode = ChartImageWrapMode.Tile; private Color _backImageTransparentColor = Color.Empty; private ChartImageAlignmentStyle _backImageAlignment = ChartImageAlignmentStyle.TopLeft; private GradientStyle _backGradientStyle = GradientStyle.None; private Color _backSecondaryColor = Color.Empty; private Color _borderColor = Color.Empty; private Color _backColor = Color.Empty; private int _borderWidth = 1; private ChartDashStyle _borderDashStyle = ChartDashStyle.Solid; private FontCache _fontCache = new FontCache(); private Font _font = null; private Color _foreColor = Color.Black; private StringAlignment _legendAlignment = StringAlignment.Near; private Docking _legendDocking = Docking.Right; private int _shadowOffset = 0; private Color _shadowColor = Color.FromArgb(128, 0, 0, 0); private bool _isTextAutoFit = true; private string _dockedToChartArea = Constants.NotSetValue; private bool _isDockedInsideChartArea = true; //*********************************************************** //** Private data members //*********************************************************** // Collection of custom and series legend items internal LegendItemsCollection legendItems = null; // Number of rows and columns private int _itemColumns = 0; // Font calculated by auto fitting internal Font autofitFont = null; // Indicates that all items in the legend should be equally spaced private bool _isEquallySpacedItems = false; // Indicate that legend rows should be drawn with isInterlaced background color. private bool _interlacedRows = false; // Legend isInterlaced rows color private Color _interlacedRowsColor = Color.Empty; // Legend offsets private Size _offset = Size.Empty; // Adjustment point used for legend animation private float _maximumLegendAutoSize = 50f; // Text length after which the legend item text will be wrapped on the next whitespace. private int _textWrapThreshold = 25; // Value used to calculate auto-fit font size from the legend Font. private int _autoFitFontSizeAdjustment = 0; // Legend column collection private LegendCellColumnCollection _cellColumns = null; // Indicates that legend items automatically added based on the exsisting // series in reversed order. private LegendItemOrder _legendItemOrder = LegendItemOrder.Auto; // Legend title text private string _title = String.Empty; // Legend title color private Color _titleForeColor = Color.Black; // Legend title back color private Color _titleBackColor = Color.Empty; // Legend title font private Font _titleFont = null; // Legend title alignment private StringAlignment _titleAlignment = StringAlignment.Center; // Legend title visual separator private LegendSeparatorStyle _titleSeparator = LegendSeparatorStyle.None; // Legend title visual separator color private Color _titleSeparatorColor = Color.Black; // Legend header visual separator private LegendSeparatorStyle _headerSeparator = LegendSeparatorStyle.None; // Legend header visual separator color private Color _headerSeparatorColor = Color.Black; // Legend table columns visual separator private LegendSeparatorStyle _itemColumnSeparator = LegendSeparatorStyle.None; // Legend table columns visual separator color private Color _itemColumnSeparatorColor = Color.Black; // Legend table column spacing calculated as a percentage of the font private int _itemColumnSpacing = 50; // Legend table column spacing calculated in relative coordinates private int _itemColumnSpacingRel = 0; // Legend title position in pixelcoordinates. // Note that legend title always docked to the top of the legend. private Rectangle _titlePosition = Rectangle.Empty; // Legend header position in pixel coordinates. private Rectangle _headerPosition = Rectangle.Empty; // Minimum font size that can be used by the legend auto-fitting algorithm private int _autoFitMinFontSize = 7; // Horizontal space left after fitting legend items private int _horizontalSpaceLeft = 0; // Vertical space left after fitting legend items private int _verticalSpaceLeft = 0; // Sub-columns sizes calculated during the fitting process private int[,] _subColumnSizes = null; // Legend item heigts private int[,] _cellHeights = null; // Number of rows per each legend table column private int[] _numberOfRowsPerColumn = null; // Number of items from the collection that should be processed private int _numberOfLegendItemsToProcess = -1; // Legend items area position in pixels private Rectangle _legendItemsAreaPosition = Rectangle.Empty; // Indicates that not all legend items were able to fit the legend private bool _legendItemsTruncated = false; // Size of the dots (pixels) that will drawn on the bottom of the legend when it is truncated private int _truncatedDotsSize = 3; // Maximum number of cells in the legend item private int _numberOfCells = -1; // Pixel size of the 'W' character internal Size singleWCharacterSize = Size.Empty; #endregion #region Constructors /// /// Legend constructor /// public Legend() { _position = new ElementPosition(this); // Initialize custom items collection _customLegends = new LegendItemsCollection(this); legendItems = new LegendItemsCollection(this); _cellColumns = new LegendCellColumnCollection(this); _font = _fontCache.DefaultFont; _titleFont = _fontCache.DefaultBoldFont; } /// /// Legend constructor /// /// The legend name. public Legend(string name) : base (name) { _position = new ElementPosition(this); // Initialize custom items collection _customLegends = new LegendItemsCollection(this); legendItems = new LegendItemsCollection(this); _cellColumns = new LegendCellColumnCollection(this); _font = _fontCache.DefaultFont; _titleFont = _fontCache.DefaultBoldFont; } #endregion #region Legend position & size methods /// /// Recalculates legend information: /// - legend items collection /// - maximum text rectangle /// /// Reference to the chart graphics. private void RecalcLegendInfo(ChartGraphics chartGraph) { // Reset some values RectangleF legendPositionRel = _position.ToRectangleF(); Rectangle legendPosition = Rectangle.Round(chartGraph.GetAbsoluteRectangle(legendPositionRel)); //*********************************************************** //** Use size of the "W" characters in current font to //** calculate legend spacing //*********************************************************** this.singleWCharacterSize = chartGraph.MeasureStringAbs("W", this.Font); Size doubleCharacterSize = chartGraph.MeasureStringAbs("WW", this.Font); this.singleWCharacterSize.Width = doubleCharacterSize.Width - this.singleWCharacterSize.Width; // Calculate left, top offset and column spacing this._offset.Width = (int)Math.Ceiling(singleWCharacterSize.Width / 2f); this._offset.Height = (int)Math.Ceiling(singleWCharacterSize.Width / 3f); // Calculate item column spacing and make sure it is dividable by 2 this._itemColumnSpacingRel = (int)(singleWCharacterSize.Width * (this._itemColumnSpacing / 100f)); if(this._itemColumnSpacingRel % 2 == 1) { this._itemColumnSpacingRel += 1; } //*********************************************************** //** Calculate how much space required for the title. //*********************************************************** this._titlePosition = Rectangle.Empty; if(this.Title.Length > 0) { // Measure title text size Size titleSize = this.GetTitleSize(chartGraph, legendPosition.Size); // Set legend title position this._titlePosition = new Rectangle( legendPosition.Location.X, legendPosition.Location.Y, legendPosition.Width, Math.Min(legendPosition.Height, titleSize.Height)); // Adjust legend items position height legendPosition.Height -= this._titlePosition.Height; // Increase title top location by border height this._titlePosition.Y += this.GetBorderSize(); } //*********************************************************** //** Calculate how much space required for the header. //*********************************************************** this._headerPosition = Rectangle.Empty; // Find the largest (height only) header Size highestHeader = Size.Empty; foreach(LegendCellColumn legendColumn in this.CellColumns) { if(legendColumn.HeaderText.Length > 0) { // Measure header text size Size headerSize = this.GetHeaderSize(chartGraph, legendColumn); // Get header with maximum height highestHeader.Height = Math.Max(highestHeader.Height, headerSize.Height); } } // Check if any headers where found if(!highestHeader.IsEmpty) { // Set legend header position this._headerPosition = new Rectangle( legendPosition.Location.X + this.GetBorderSize() + this._offset.Width, legendPosition.Location.Y + this._titlePosition.Height, legendPosition.Width - (this.GetBorderSize() + this._offset.Width) * 2, Math.Min(legendPosition.Height - this._titlePosition.Height, highestHeader.Height)); this._headerPosition.Height = Math.Max(this._headerPosition.Height, 0); // Adjust legend items position height legendPosition.Height -= this._headerPosition.Height; // Increase header top location by border height this._headerPosition.Y += this.GetBorderSize(); } //*********************************************************** //** Calculate size available for all legend items //*********************************************************** this._legendItemsAreaPosition = new Rectangle( legendPosition.X + this._offset.Width + this.GetBorderSize(), legendPosition.Y + this._offset.Height + this.GetBorderSize() + this._titlePosition.Height + this._headerPosition.Height, legendPosition.Width - 2 * (this._offset.Width + this.GetBorderSize()), legendPosition.Height - 2 * (this._offset.Height + this.GetBorderSize()) ); //*********************************************************** //** Calculate number of rows and columns depending on //** the legend style //*********************************************************** this.GetNumberOfRowsAndColumns( chartGraph, this._legendItemsAreaPosition.Size, -1, out this._numberOfRowsPerColumn, out this._itemColumns, out this._horizontalSpaceLeft, out this._verticalSpaceLeft); //*********************************************************** //** Try to fit all legend item cells reducing the font size //*********************************************************** // Reset auto-fit font adjustment value and truncated legend flag this._autoFitFontSizeAdjustment = 0; this._legendItemsTruncated = false; // Check if legend items fit into the legend area bool autoFitDone = (this._horizontalSpaceLeft >= 0 && this._verticalSpaceLeft >= 0); // Calculate total number of items fit and make sure we fit all of them this._numberOfLegendItemsToProcess = this.legendItems.Count; int itemsFit = 0; for(int index = 0; index < this._itemColumns; index++) { itemsFit += this._numberOfRowsPerColumn[index]; } if(itemsFit < this._numberOfLegendItemsToProcess) { autoFitDone = false; } // If items do not fit try reducing font or number of legend items this.autofitFont = this.Font; if(!autoFitDone) { do { // Check if legend item font size can be reduced if(this.IsTextAutoFit && (this.Font.Size - this._autoFitFontSizeAdjustment) > this._autoFitMinFontSize) { // Reduce font size by one ++this._autoFitFontSizeAdjustment; // Calculate new font size int newFontSize = (int)Math.Round(this.Font.Size - this._autoFitFontSizeAdjustment); if(newFontSize < 1) { // Font can't be less than size 1 newFontSize = 1; } // Create new font this.autofitFont = this.Common.ChartPicture.FontCache.GetFont( this.Font.FontFamily, newFontSize, this.Font.Style, this.Font.Unit); // Calculate number of rows and columns this.GetNumberOfRowsAndColumns( chartGraph, this._legendItemsAreaPosition.Size, -1, out this._numberOfRowsPerColumn, out this._itemColumns, out this._horizontalSpaceLeft, out this._verticalSpaceLeft); autoFitDone = (this._horizontalSpaceLeft >= 0 && this._verticalSpaceLeft >= 0); // Calculate total number of items fit and make sure we fit all of them itemsFit = 0; for(int index = 0; index < this._itemColumns; index++) { itemsFit += this._numberOfRowsPerColumn[index]; } if(itemsFit < this._numberOfLegendItemsToProcess) { autoFitDone = false; } } else { // If font size cannot be reduced start removing legend items if(this._numberOfLegendItemsToProcess > 2) { // Handle case of 1 column that do not fit horizontally if(this._itemColumns == 1 && (this._horizontalSpaceLeft < 0 && this._verticalSpaceLeft >= 0)) { autoFitDone = true; this._numberOfLegendItemsToProcess = Math.Min(this._numberOfLegendItemsToProcess, this._numberOfRowsPerColumn[0]); } // Handle case of 1 row that do not fit vertically else if(this.GetMaximumNumberOfRows() == 1 && (this._verticalSpaceLeft < 0 && this._horizontalSpaceLeft >= 0)) { autoFitDone = true; this._numberOfLegendItemsToProcess = Math.Min(this._numberOfLegendItemsToProcess, this._itemColumns); } else { // Adjust legend items area height by size required to show // visually (dots) that legend is truncated if(!this._legendItemsTruncated) { this._legendItemsAreaPosition.Height -= this._truncatedDotsSize; } // Remove last legend item this._legendItemsTruncated = true; --this._numberOfLegendItemsToProcess; // RecalculateAxesScale number of rows and columns this.GetNumberOfRowsAndColumns( chartGraph, this._legendItemsAreaPosition.Size, this._numberOfLegendItemsToProcess, out this._numberOfRowsPerColumn, out this._itemColumns); } // Make sure we show truncated legend symbols when not all items shown if(autoFitDone && !this._legendItemsTruncated && this._numberOfLegendItemsToProcess < this.legendItems.Count) { // Adjust legend items area height by size required to show // visually (dots) that legend is truncated this._legendItemsAreaPosition.Height -= this._truncatedDotsSize; // Legend is truncated this._legendItemsTruncated = true; } } else { autoFitDone = true; } // Check if legend items fit into the legend area if(!autoFitDone) { autoFitDone = this.CheckLegendItemsFit( chartGraph, this._legendItemsAreaPosition.Size, this._numberOfLegendItemsToProcess, this._autoFitFontSizeAdjustment, this._itemColumns, this._numberOfRowsPerColumn, out this._subColumnSizes, out this._cellHeights, out this._horizontalSpaceLeft, out this._verticalSpaceLeft); } } } while(!autoFitDone); } //*********************************************************** //** Calculate position of all cells //*********************************************************** // Calculate item vertical spacing in relative coordinates but rounded on pixel boundary Size itemHalfSpacing = Size.Empty; if(this._verticalSpaceLeft > 0) { itemHalfSpacing.Height = (int)(this._verticalSpaceLeft / this.GetMaximumNumberOfRows() / 2); } if(this._horizontalSpaceLeft > 0) { itemHalfSpacing.Width = (int)(_horizontalSpaceLeft / 2); } // Iterate through all legend items int currentColumn = 0; int currentRow = 0; if(this._numberOfLegendItemsToProcess < 0) { this._numberOfLegendItemsToProcess = this.legendItems.Count; } for(int legendItemIndex = 0; legendItemIndex < this._numberOfLegendItemsToProcess; legendItemIndex++) { LegendItem legendItem = this.legendItems[legendItemIndex]; // Iterate through legend item cells for(int cellIndex = 0; cellIndex < legendItem.Cells.Count; cellIndex++) { // Get legend cell LegendCell legendCell = legendItem.Cells[cellIndex]; // Calculate cell position Rectangle cellPosition = this.GetCellPosition(currentColumn, currentRow, cellIndex, itemHalfSpacing); // Check if current cell spans through more than 1 cell int overlappedCellsNumber = 0; if(legendCell.CellSpan > 1) { for(int spanIndex = 1; spanIndex < legendCell.CellSpan && (cellIndex + spanIndex) < legendItem.Cells.Count; spanIndex++) { // Calculate overlapped cell position Rectangle overlappedCellPosition = this.GetCellPosition(currentColumn, currentRow, cellIndex + spanIndex, itemHalfSpacing); // Adjust current cell right position if(cellPosition.Right < overlappedCellPosition.Right) { cellPosition.Width += overlappedCellPosition.Right - cellPosition.Right; } // Increase number of overlapped cells ++overlappedCellsNumber; // Set empty size for the overlapped cells LegendCell overlappedLegendCell = legendItem.Cells[cellIndex + spanIndex]; overlappedLegendCell.SetCellPosition( currentRow, Rectangle.Empty, this.singleWCharacterSize); } } // Make sure cell is drawn inside the legend cellPosition.Intersect(this._legendItemsAreaPosition); // Set cell object position legendCell.SetCellPosition( currentRow, cellPosition, this.singleWCharacterSize); // Skip overlapped cells cellIndex += overlappedCellsNumber; } // Advance to the next row/column. Break if number of legend items exceed // number of availabale rows/columns. ++currentRow; if(currentRow >= this._numberOfRowsPerColumn[currentColumn]) { ++currentColumn; currentRow = 0; if(currentColumn >= this._itemColumns) { break; } } } } /// /// Gets single cell position in relative coordinates. /// /// Cell column index. /// Cell row index. /// Index of the cell in the legend item. /// Half legend item spacing in relative coordinates. /// private Rectangle GetCellPosition( int columnIndex, int rowIndex, int cellIndex, Size itemHalfSpacing) { Rectangle cellPosition = this._legendItemsAreaPosition; //***************************************************************** //** Get cell Top location //***************************************************************** for(int index = 0; index < rowIndex; index++) { cellPosition.Y += this._cellHeights[columnIndex, index]; } if(itemHalfSpacing.Height > 0) { cellPosition.Y += itemHalfSpacing.Height * rowIndex * 2 + itemHalfSpacing.Height; } //***************************************************************** //** Get cell Left location //***************************************************************** // Add extar space left after auto fitting if(this._horizontalSpaceLeft > 0) { cellPosition.X += itemHalfSpacing.Width; } // Calculate how many sub-columns (cells) this legend has int numberOfSubColumns = this.GetNumberOfCells(); // Iterate through all prev. columns for(int index = 0; index < columnIndex; index++) { // Add width of previous columns for(int subColumnIndex = 0; subColumnIndex < numberOfSubColumns; subColumnIndex++) { cellPosition.X += this._subColumnSizes[index, subColumnIndex]; } // Add width of separator for the previous columns cellPosition.X += this.GetSeparatorSize(this.ItemColumnSeparator).Width; } // Add width of current column cells for(int subColumnIndex = 0; subColumnIndex < cellIndex; subColumnIndex++) { cellPosition.X += this._subColumnSizes[columnIndex, subColumnIndex]; } //***************************************************************** //** Get cell Height //***************************************************************** cellPosition.Height = this._cellHeights[columnIndex, rowIndex]; //***************************************************************** //** Get cell Width //***************************************************************** cellPosition.Width = this._subColumnSizes[columnIndex, cellIndex]; return cellPosition; } /// /// Calculates the optimal size of the legend. /// /// Chart graphics object. /// Max size for the legend. /// Legend optimal size. private SizeF GetOptimalSize(ChartGraphics chartGraph, SizeF maxSizeRel) { // Reset some values this._offset = Size.Empty; this._itemColumns = 0; this._horizontalSpaceLeft = 0; this._verticalSpaceLeft = 0; this._subColumnSizes = null; this._numberOfRowsPerColumn = null; this._cellHeights = null; this.autofitFont = null; this._autoFitFontSizeAdjustment = 0; this._numberOfCells = -1; this._numberOfLegendItemsToProcess = -1; Size optimalSize = Size.Empty; // Convert to pixels SizeF maxSizeAbs = chartGraph.GetAbsoluteSize(maxSizeRel); Size maxSize = new Size((int)maxSizeAbs.Width, (int)maxSizeAbs.Height); // Clear all legend item cells cached information foreach(LegendItem legendItem in this.legendItems) { foreach(LegendCell cell in legendItem.Cells) { cell.ResetCache(); } } // Check if legend is enabled if(this.IsEnabled()) { // Add all series legend into items collection and then add custom legend items FillLegendItemsCollection(); // Call a notification event, so that legend items collection can be modified by user this.Common.Chart.CallOnCustomizeLegend(legendItems, this.Name); // Check if there are any items in the legend if(this.legendItems.Count > 0) { //*********************************************************** //** Use size of the "W" character in current font to //** calculate legend spacing //*********************************************************** this.singleWCharacterSize = chartGraph.MeasureStringAbs("W", this.Font); Size doubleCharacterSize = chartGraph.MeasureStringAbs("WW", this.Font); this.singleWCharacterSize.Width = doubleCharacterSize.Width - this.singleWCharacterSize.Width; // Calculate left, top offset and column spacing this._offset.Width = (int)Math.Ceiling(singleWCharacterSize.Width / 2f); this._offset.Height = (int)Math.Ceiling(singleWCharacterSize.Width / 3f); this._itemColumnSpacingRel = (int)(singleWCharacterSize.Width * (this._itemColumnSpacing / 100f)); if(this._itemColumnSpacingRel % 2 == 1) { this._itemColumnSpacingRel += 1; } //*********************************************************** //** Add size required for the legend tile //*********************************************************** Size titleSize = Size.Empty; if(this.Title.Length > 0) { titleSize = this.GetTitleSize(chartGraph, maxSize); } //*********************************************************** //** Add size required for the legend header //*********************************************************** Size highestHeader = Size.Empty; foreach(LegendCellColumn legendColumn in this.CellColumns) { if(legendColumn.HeaderText.Length > 0) { // Measure header text size Size headerSize = this.GetHeaderSize(chartGraph, legendColumn); // Get header with maximum height highestHeader.Height = Math.Max(highestHeader.Height, headerSize.Height); } } //*********************************************************** //** Calculate size available for legend items //*********************************************************** Size legenItemsMaxSize = maxSize; legenItemsMaxSize.Width -= 2 * (this._offset.Width + this.GetBorderSize()); legenItemsMaxSize.Height -= 2 * (this._offset.Height + this.GetBorderSize()); legenItemsMaxSize.Height -= titleSize.Height; legenItemsMaxSize.Height -= highestHeader.Height; //*********************************************************** //** Calculate number of rows and columns depending on //** the legend style //*********************************************************** this._autoFitFontSizeAdjustment = 0; this.autofitFont = this.Font; int vertSpaceLeft = 0; int horizSpaceLeft = 0; bool reduceFont = this.IsTextAutoFit; bool autoFit = false; do { // Get number of columns and rows that we can fit in the legend this.GetNumberOfRowsAndColumns( chartGraph, legenItemsMaxSize, -1, out this._numberOfRowsPerColumn, out this._itemColumns, out horizSpaceLeft, out vertSpaceLeft); // Calculate total number of items fit and make sure we fit all of them int itemsFit = 0; for(int index = 0; index < this._itemColumns; index++) { itemsFit += this._numberOfRowsPerColumn[index]; } autoFit = (horizSpaceLeft >= 0 && vertSpaceLeft >= 0 && itemsFit >= this.legendItems.Count); // Check if items fit if(reduceFont && !autoFit) { if((this.Font.Size - this._autoFitFontSizeAdjustment) > this._autoFitMinFontSize) { // Reduce font size by one ++this._autoFitFontSizeAdjustment; // Calculate new font size int newFontSize = (int)Math.Round(this.Font.Size - this._autoFitFontSizeAdjustment); if(newFontSize < 1) { // Font can't be less than size 1 newFontSize = 1; } // Create new font this.autofitFont = this.Common.ChartPicture.FontCache.GetFont( this.Font.FontFamily, newFontSize, this.Font.Style, this.Font.Unit); } else { reduceFont = false; } } } while(reduceFont && !autoFit); // Slightly reduce used space horizSpaceLeft -= Math.Min(4, horizSpaceLeft); vertSpaceLeft -= Math.Min(2, vertSpaceLeft); //*********************************************************** //** Calculate legend size //*********************************************************** optimalSize.Width = (legenItemsMaxSize.Width - horizSpaceLeft); optimalSize.Width = Math.Max(optimalSize.Width, titleSize.Width); optimalSize.Width += 2 * (this._offset.Width + this.GetBorderSize()); optimalSize.Height = (legenItemsMaxSize.Height - vertSpaceLeft) + titleSize.Height + highestHeader.Height; optimalSize.Height += 2 * (this._offset.Height + this.GetBorderSize()); // Adjust legend items area height by size required to show // visually (dots) that legend is truncated if(horizSpaceLeft < 0 || vertSpaceLeft < 0) { optimalSize.Height += this._truncatedDotsSize; } //*********************************************************** //** Make sure legend size do not exceed max. value //*********************************************************** if(optimalSize.Width > maxSize.Width) { optimalSize.Width = maxSize.Width; } if(optimalSize.Height > maxSize.Height) { optimalSize.Height = maxSize.Height; } if(optimalSize.Width < 0) { optimalSize.Width = 0; } if(optimalSize.Height < 0) { optimalSize.Height = 0; } } } // Convert result size from pixel to relative coordinates return chartGraph.GetRelativeSize(optimalSize); } /// /// Recalculates legend position. /// /// Chart graphics used. /// Area where the legend should be positioned. /// Spacing size as a percentage of the area. internal void CalcLegendPosition( ChartGraphics chartGraph, ref RectangleF chartAreasRectangle, float elementSpacing) { RectangleF legendPosition = new RectangleF(); // Get optimal legend size SizeF maxSize = new SizeF(chartAreasRectangle.Width - 2*elementSpacing, chartAreasRectangle.Height - 2*elementSpacing); if (this.DockedToChartArea == Constants.NotSetValue) { // Note: 'maxLegendSize' parameter is ignored. New legend property // 'maximumLegendAutoSize' is used instead. if(this.Docking == Docking.Top || this.Docking == Docking.Bottom) { maxSize.Height = (maxSize.Height / 100F) * this._maximumLegendAutoSize; } else { maxSize.Width = (maxSize.Width / 100F) * this._maximumLegendAutoSize; } } if(maxSize.Width <= 0 || maxSize.Height <= 0) { return; } SizeF legendSize = this.GetOptimalSize(chartGraph, maxSize); legendPosition.Height = legendSize.Height; legendPosition.Width = legendSize.Width; if(float.IsNaN(legendSize.Height) || float.IsNaN(legendSize.Width)) { return; } // Calculate legend position if(this.Docking == Docking.Top) { legendPosition.Y = chartAreasRectangle.Y + elementSpacing; if(this.Alignment == StringAlignment.Near) { legendPosition.X = chartAreasRectangle.X + elementSpacing; } else if(this.Alignment == StringAlignment.Far) { legendPosition.X = chartAreasRectangle.Right - legendSize.Width - elementSpacing; } else if(this.Alignment == StringAlignment.Center) { legendPosition.X = chartAreasRectangle.X + (chartAreasRectangle.Width - legendSize.Width) / 2F; } // Adjust position of the chart area(s) chartAreasRectangle.Height -= legendPosition.Height + elementSpacing; chartAreasRectangle.Y = legendPosition.Bottom; } else if(this.Docking == Docking.Bottom) { legendPosition.Y = chartAreasRectangle.Bottom - legendSize.Height - elementSpacing; if(this.Alignment == StringAlignment.Near) { legendPosition.X = chartAreasRectangle.X + elementSpacing; } else if(this.Alignment == StringAlignment.Far) { legendPosition.X = chartAreasRectangle.Right - legendSize.Width - elementSpacing; } else if(this.Alignment == StringAlignment.Center) { legendPosition.X = chartAreasRectangle.X + (chartAreasRectangle.Width - legendSize.Width) / 2F; } // Adjust position of the chart area(s) chartAreasRectangle.Height -= legendPosition.Height + elementSpacing; } if(this.Docking == Docking.Left) { legendPosition.X = chartAreasRectangle.X + elementSpacing; if(this.Alignment == StringAlignment.Near) { legendPosition.Y = chartAreasRectangle.Y + elementSpacing; } else if(this.Alignment == StringAlignment.Far) { legendPosition.Y = chartAreasRectangle.Bottom - legendSize.Height - elementSpacing; } else if(this.Alignment == StringAlignment.Center) { legendPosition.Y = chartAreasRectangle.Y + (chartAreasRectangle.Height - legendSize.Height) / 2F; } // Adjust position of the chart area(s) chartAreasRectangle.Width -= legendPosition.Width + elementSpacing; chartAreasRectangle.X = legendPosition.Right; } if(this.Docking == Docking.Right) { legendPosition.X = chartAreasRectangle.Right - legendSize.Width - elementSpacing; if(this.Alignment == StringAlignment.Near) { legendPosition.Y = chartAreasRectangle.Y + elementSpacing; } else if(this.Alignment == StringAlignment.Far) { legendPosition.Y = chartAreasRectangle.Bottom - legendSize.Height - elementSpacing; } else if(this.Alignment == StringAlignment.Center) { legendPosition.Y = chartAreasRectangle.Y + (chartAreasRectangle.Height - legendSize.Height) / 2F; } // Adjust position of the chart area(s) chartAreasRectangle.Width -= legendPosition.Width + elementSpacing; } this.Position.SetPositionNoAuto(legendPosition.X, legendPosition.Y, legendPosition.Width, legendPosition.Height); } /// /// Get number of columns and rows that can be fit in specified size. /// /// Chart graphics. /// Legend size. /// Number of legend items to check. /// Array with number of rows per each column. /// Returns number of columns. private void GetNumberOfRowsAndColumns( ChartGraphics chartGraph, Size legendSize, int numberOfItemsToCheck, out int[] numberOfRowsPerColumn, out int columnNumber) { int horSpaceLeft = 0; int vertSpaceLeft = 0; this.GetNumberOfRowsAndColumns( chartGraph, legendSize, numberOfItemsToCheck, out numberOfRowsPerColumn, out columnNumber, out horSpaceLeft, out vertSpaceLeft); } /// /// Get number of columns and rows that can be fit in specified size. /// /// Chart graphics. /// Legend size. /// Legend items number to check. /// Array with number of rows per each column. /// Returns number of columns. /// Returns horizontal spacing left. /// Returns vertical spacing left. private void GetNumberOfRowsAndColumns( ChartGraphics chartGraph, Size legendSize, int numberOfItemsToCheck, out int[] numberOfRowsPerColumn, out int columnNumber, out int horSpaceLeft, out int vertSpaceLeft) { // Initialize output parameters numberOfRowsPerColumn = null; columnNumber = 1; horSpaceLeft = 0; vertSpaceLeft = 0; // If number of items to check is nor set use total number of items in the collection if(numberOfItemsToCheck < 0) { numberOfItemsToCheck = legendItems.Count; } // Check legend style if(this.LegendStyle == LegendStyle.Column || numberOfItemsToCheck <= 1) { columnNumber = 1; numberOfRowsPerColumn = new int[] { numberOfItemsToCheck }; } else if(this.LegendStyle == LegendStyle.Row) { columnNumber = numberOfItemsToCheck; numberOfRowsPerColumn = new int[columnNumber]; for(int index = 0; index < columnNumber; index++) { numberOfRowsPerColumn[index] = 1; } } else if(this.LegendStyle == LegendStyle.Table) { // Start with 1 column and 1 row columnNumber = 1; numberOfRowsPerColumn = new int[] { 1 }; // Get legend table style and adjust number of columns and rows accordinly LegendTableStyle tableStyle = this.GetLegendTableStyle(chartGraph); //********************************************************************************* //** Tall table layout //********************************************************************************* if(tableStyle == LegendTableStyle.Tall) { // Iterate from second item trying to add them and check if their fit bool exitLoop = false; int legendItemIndex = 1; for(legendItemIndex = 1; !exitLoop && legendItemIndex < numberOfItemsToCheck; legendItemIndex ++) { // Try to increase number of rows in the current column ++numberOfRowsPerColumn[columnNumber - 1]; // Check if legend items fit into the legend area bool autoFitDone = this.CheckLegendItemsFit( chartGraph, legendSize, legendItemIndex + 1, this._autoFitFontSizeAdjustment, columnNumber, numberOfRowsPerColumn, out this._subColumnSizes, out this._cellHeights, out horSpaceLeft, out vertSpaceLeft); // Check if we fit or if we have just one column that do not fit // horizontally but still have vertical space. if(autoFitDone || ( (columnNumber == 1 || horSpaceLeft < 0) && vertSpaceLeft > 0) ) { // Continue adding rows to the current column continue; } else { // Reduce number of rows in the current column if(numberOfRowsPerColumn[columnNumber - 1] > 1) { --numberOfRowsPerColumn[columnNumber - 1]; } // Get half of average column width int averageColumnWidth = 0; if(horSpaceLeft > 0) { averageColumnWidth = (int)Math.Round((double)(legendSize.Width - horSpaceLeft) / columnNumber) / 2; } // Check if number of columns can be increased if(columnNumber < 50 && horSpaceLeft >= averageColumnWidth) { // Add new column ++columnNumber; // Resize array that stores number of rows per column int[] tempArray = numberOfRowsPerColumn; numberOfRowsPerColumn = new int[columnNumber]; for(int index = 0; index < tempArray.Length; index++) { numberOfRowsPerColumn[index] = tempArray[index]; } numberOfRowsPerColumn[columnNumber - 1] = 1; // If last legend item is moved into a new column // call the auto fitting method before leaving the loop if(legendItemIndex == numberOfItemsToCheck - 1) { this.CheckLegendItemsFit( chartGraph, legendSize, legendItemIndex + 1, this._autoFitFontSizeAdjustment, columnNumber, numberOfRowsPerColumn, out this._subColumnSizes, out this._cellHeights, out horSpaceLeft, out vertSpaceLeft); } } else { exitLoop = true; } } } // Check if we end up with legend with multiple columns // where last column has sinificantly lower height of all rows if(columnNumber > 1) { // Try reducing number of rows in the "tall" calumns and move them // into the last column. bool done = false; while(!done) { // By default no more iterations required done = true; // Find maximum column height not taking the last row in consideration int maxColumnHeight = -1; for(int columnIndex = 0; columnIndex < columnNumber; columnIndex++) { // Calculate current column height not taking the last row in consideration int columnHeight = 0; for(int rowIndex = 0; rowIndex < this._numberOfRowsPerColumn[columnIndex] - 1; rowIndex++) { columnHeight += this._cellHeights[columnIndex, rowIndex]; } // Find maximum height maxColumnHeight = Math.Max(maxColumnHeight, columnHeight); } // Calculate total height of items in the last row int totalHieghtOfItemInLastRow = 0; for(int columnIndex = 0; columnIndex < (columnNumber - 1); columnIndex++) { if(this._numberOfRowsPerColumn[columnIndex] > 1) { totalHieghtOfItemInLastRow += this._cellHeights[columnIndex, this._numberOfRowsPerColumn[columnIndex] - 1]; } } // Check if rows are available for removal if(totalHieghtOfItemInLastRow > 0) { // Get last column height int lastColumnHeight = this.GetColumnHeight(columnNumber - 1); // Check if all items in the last row can vertically fit in last column if( (lastColumnHeight + totalHieghtOfItemInLastRow) <= maxColumnHeight ) { // Reduce number of rows in all columns except last int itemsToAdd = 0; for(int columnIndex = 0; columnIndex < (columnNumber - 1); columnIndex++) { if(this._numberOfRowsPerColumn[columnIndex] > 1) { --this._numberOfRowsPerColumn[columnIndex]; ++itemsToAdd; } } // Add rows to last column if(itemsToAdd > 0) { // Add roes into the last column this._numberOfRowsPerColumn[columnNumber - 1] += itemsToAdd; // Check if legend items fit into the legend area bool autoFitDone = this.CheckLegendItemsFit( chartGraph, legendSize, legendItemIndex + 1, this._autoFitFontSizeAdjustment, columnNumber, numberOfRowsPerColumn, out this._subColumnSizes, out this._cellHeights, out horSpaceLeft, out vertSpaceLeft); // Try doing one more time done = false; } } } } } } //********************************************************************************* //** Wide table layout //********************************************************************************* else if(tableStyle == LegendTableStyle.Wide) { // Iterate from second item trying to add them and check if they fit bool exitLoop = false; int legendItemIndex = 1; for(legendItemIndex = 1; !exitLoop && legendItemIndex < numberOfItemsToCheck; legendItemIndex ++) { // Try to increase number of columns ++columnNumber; // Resize array that stores number of rows per column int[] tempArray = numberOfRowsPerColumn; numberOfRowsPerColumn = new int[columnNumber]; for(int index = 0; index < tempArray.Length; index++) { numberOfRowsPerColumn[index] = tempArray[index]; } numberOfRowsPerColumn[columnNumber - 1] = 1; // Check if legend items fit into the legend area bool autoFitDone = this.CheckLegendItemsFit( chartGraph, legendSize, legendItemIndex + 1, this._autoFitFontSizeAdjustment, columnNumber, numberOfRowsPerColumn, out this._subColumnSizes, out this._cellHeights, out horSpaceLeft, out vertSpaceLeft); // Check if we fit or if we have just one row that do not fit // vertically but still have horizontal space. if(autoFitDone || ( (this.GetMaximumNumberOfRows(numberOfRowsPerColumn) == 1 || vertSpaceLeft < 0) && horSpaceLeft > 0) ) { // Continue adding columns continue; } else { // Remove columns and increase number of rows bool columnFitting = true; while(columnFitting) { columnFitting = false; // If we can't fit current number of columns reduce current column number int rowsToAdd = 0; if(columnNumber > 1) { rowsToAdd = numberOfRowsPerColumn[columnNumber - 1]; --columnNumber; // Resize array that stores number of rows per column tempArray = numberOfRowsPerColumn; numberOfRowsPerColumn = new int[columnNumber]; for(int index = 0; index < columnNumber; index++) { numberOfRowsPerColumn[index] = tempArray[index]; } } // We may need to add more than 1 row for(int indexRowToAdd = 0; indexRowToAdd < rowsToAdd; indexRowToAdd++) { // Find first column with smallest height int smallestColumnIndex = -1; int columnMinHeight = int.MaxValue; for(int columnIndex = 0; columnIndex < columnNumber; columnIndex++) { int columnHeight = this.GetColumnHeight(columnIndex); int nextColumnFirstItemHeight = 0; if(columnIndex < columnNumber - 1) { nextColumnFirstItemHeight = this._cellHeights[columnIndex + 1, 0]; } if(columnHeight < columnMinHeight && (columnHeight + nextColumnFirstItemHeight) < legendSize.Height) { // Remember column index and height columnMinHeight = columnHeight; smallestColumnIndex = columnIndex; } } // No more items can fit if(smallestColumnIndex < 0) { // Check if legend items fit into the legend area autoFitDone = this.CheckLegendItemsFit( chartGraph, legendSize, legendItemIndex + 1, this._autoFitFontSizeAdjustment, columnNumber, numberOfRowsPerColumn, out this._subColumnSizes, out this._cellHeights, out horSpaceLeft, out vertSpaceLeft); exitLoop = true; break; } // Add new row to the smallest column ++numberOfRowsPerColumn[smallestColumnIndex]; // Check if next column will be removed if it contains only 1 row if(smallestColumnIndex < (columnNumber - 1)) { if(numberOfRowsPerColumn[smallestColumnIndex + 1] == 1) { // Shift number of rows per column tempArray = numberOfRowsPerColumn; for(int index = smallestColumnIndex + 1; index < tempArray.Length - 1; index++) { numberOfRowsPerColumn[index] = tempArray[index + 1]; } numberOfRowsPerColumn[columnNumber - 1] = 1; } } // Check if legend items fit into the legend area autoFitDone = this.CheckLegendItemsFit( chartGraph, legendSize, legendItemIndex + 1, this._autoFitFontSizeAdjustment, columnNumber, numberOfRowsPerColumn, out this._subColumnSizes, out this._cellHeights, out horSpaceLeft, out vertSpaceLeft); } // If there is more than 1 column and items do not fit // horizontally - reduce number of columns. if(!autoFitDone && horSpaceLeft < 0f && columnNumber > 1) { columnFitting = true; } } } } } } // Check if items fit and how much empty space left this.CheckLegendItemsFit( chartGraph, legendSize, -1, this._autoFitFontSizeAdjustment, columnNumber, numberOfRowsPerColumn, out this._subColumnSizes, out this._cellHeights, out horSpaceLeft, out vertSpaceLeft); } /// /// Gets column height. /// /// Index of the column to get the height for. /// Column height in relative coordinates. private int GetColumnHeight(int columnIndex) { // Calculate current column height int columnHeight = 0; for(int rowIndex = 0; rowIndex < this._numberOfRowsPerColumn[columnIndex]; rowIndex++) { columnHeight += this._cellHeights[columnIndex, rowIndex]; } return columnHeight; } /// /// Checks if legend background is selected. /// internal void SelectLegendBackground() { Common.HotRegionsList.AddHotRegion(this.Position.ToRectangleF(), this, ChartElementType.LegendArea, true); } #endregion Legend position & size methods #region Legend Items Fitting Methods /// /// Gets maximum number of rows in all columns. /// /// Maximum number of rows. private int GetMaximumNumberOfRows() { return this.GetMaximumNumberOfRows(this._numberOfRowsPerColumn); } /// /// Gets maximum number of rows in all columns. /// /// Array that stores number of rows per column. /// Maximum number of rows. private int GetMaximumNumberOfRows(int[] rowsPerColumn) { // Find column with maximum number of rows int maxNumberOfColumns = 0; if(rowsPerColumn != null) { for(int columnIndex = 0; columnIndex < rowsPerColumn.Length; columnIndex++) { maxNumberOfColumns = Math.Max(maxNumberOfColumns, rowsPerColumn[columnIndex]); } } return maxNumberOfColumns; } /// /// Checks if specified legend will fit the specified size. /// /// Chart graphics. /// Area that legend items must fit. /// Number of items that should be fitted. /// Number of points the standard legend font is reduced by auto-fitting algorithm. /// Legend column number. /// Array of number of rows per column. /// Returns array of sub-column size. /// Returns array of cell heights. /// Returns horizontal space left. /// Returns vertical space left. /// True if items fit. private bool CheckLegendItemsFit( ChartGraphics graph, Size legendItemsAreaSize, int numberOfItemsToCheck, int fontSizeReducedBy, int numberOfColumns, int[] numberOfRowsPerColumn, out int[,] subColumnSizes, out int[,] cellHeights, out int horizontalSpaceLeft, out int verticalSpaceLeft) { bool fitFlag = true; // Initialize output values horizontalSpaceLeft = 0; verticalSpaceLeft = 0; // Use current legend item count if number of items to check is not specified if(numberOfItemsToCheck < 0) { numberOfItemsToCheck = this.legendItems.Count; } // Calculate how many sub-columns (cells) this legend has int numberOfSubColumns = this.GetNumberOfCells(); // Each column may have its own number of rows. Calculate the maximum number of rows. int maxNumberOfRows = this.GetMaximumNumberOfRows(numberOfRowsPerColumn); // Create multidimensional arrays that will be holding the widths and heightsof all // individual cells. First dimension will be the legend column index, second dimension // is row index and the third is sub-column (cell) index. int[,,] cellWidths = new int[numberOfColumns, maxNumberOfRows, numberOfSubColumns]; cellHeights = new int[numberOfColumns, maxNumberOfRows]; //************************************************************************* //** Measure legend font single character //************************************************************************* this.singleWCharacterSize = graph.MeasureStringAbs("W", (this.autofitFont == null) ? this.Font : this.autofitFont); Size doubleCharacterSize = graph.MeasureStringAbs("WW", (this.autofitFont == null) ? this.Font : this.autofitFont); this.singleWCharacterSize.Width = doubleCharacterSize.Width - this.singleWCharacterSize.Width; //************************************************************************* //** Iterate through all legend items and measure each individual cell //************************************************************************* int currentColumn = 0; int currentRow = 0; for(int legendItemIndex = 0; legendItemIndex < numberOfItemsToCheck; legendItemIndex++) { LegendItem legendItem = this.legendItems[legendItemIndex]; // Iterate through legend item cells int numberOfCellsToSkip = 0; for(int cellIndex = 0; cellIndex < legendItem.Cells.Count; cellIndex++) { // Get legend cell LegendCell legendCell = legendItem.Cells[cellIndex]; // Get assocated legend column object (may be NULL) LegendCellColumn legendColumn = null; if(cellIndex < this.CellColumns.Count) { legendColumn = this.CellColumns[cellIndex]; } // Check if current cell should be skipped becuse it's overlapped // by the previous sell that uses CellSpan. if(numberOfCellsToSkip > 0) { // Put size (-1) for the cells that follow a cell using ColumnSpan cellWidths[currentColumn, currentRow, cellIndex] = -1; --numberOfCellsToSkip; continue; } // Check if current cell uses CellSpan if(legendCell.CellSpan > 1) { numberOfCellsToSkip = legendCell.CellSpan - 1; } // Measure cell and store the value in the array Size cellSize = legendCell.MeasureCell( graph, fontSizeReducedBy, (this.autofitFont == null) ? this.Font : this.autofitFont, this.singleWCharacterSize); // Check for column maximum/minimum cell width restrictions if(legendColumn != null) { if(legendColumn.MinimumWidth >= 0) { cellSize.Width = (int)Math.Max(cellSize.Width, legendColumn.MinimumWidth * singleWCharacterSize.Width / 100f); } if(legendColumn.MaximumWidth >= 0) { cellSize.Width = (int)Math.Min(cellSize.Width, legendColumn.MaximumWidth * singleWCharacterSize.Width / 100f); } } // Store cell size in arrays cellWidths[currentColumn, currentRow, cellIndex] = cellSize.Width; if(cellIndex == 0) { cellHeights[currentColumn, currentRow] = cellSize.Height; } else { cellHeights[currentColumn, currentRow] = Math.Max(cellHeights[currentColumn, currentRow], cellSize.Height); } } // Advance to the next row/column. Break if number of legend items exceed // number of availabale rows/columns. ++currentRow; if(currentRow >= numberOfRowsPerColumn[currentColumn]) { ++currentColumn; currentRow = 0; if(currentColumn >= numberOfColumns) { // Check if we were able to fit all the items if(legendItemIndex < numberOfItemsToCheck - 1) { fitFlag = false; } break; } } } //************************************************************************* //** For each sub-column get the maximum cell width //************************************************************************* subColumnSizes = new int[numberOfColumns, numberOfSubColumns]; bool secondIterationRequired = false; for(currentColumn = 0; currentColumn < numberOfColumns; currentColumn++) { for(int currentSubColumn = 0; currentSubColumn < numberOfSubColumns; currentSubColumn++) { int width = 0; for(currentRow = 0; currentRow < numberOfRowsPerColumn[currentColumn]; currentRow++) { // Get current cell size int cellWidth = cellWidths[currentColumn, currentRow, currentSubColumn]; // Skip overlapped cells and cells that use ColumnSpan during the // first iteration. Their size will be determined during the // second iteration. if(cellWidth < 0) { secondIterationRequired = true; continue; } if(currentSubColumn + 1 < numberOfSubColumns) { int nextCellWidth = cellWidths[currentColumn, currentRow, currentSubColumn + 1]; if(nextCellWidth < 0) { continue; } } // Get maximum width width = Math.Max(width, cellWidth ); } // Store maximum width in the array subColumnSizes[currentColumn, currentSubColumn] = width; } } //************************************************************************* //** If leagend header text is used check if it fits into the currenly //** calculated sub-column sizes. //************************************************************************* for(currentColumn = 0; currentColumn < numberOfColumns; currentColumn++) { for(int currentSubColumn = 0; currentSubColumn < numberOfSubColumns; currentSubColumn++) { if(currentSubColumn < this.CellColumns.Count) { LegendCellColumn legendColumn = this.CellColumns[currentSubColumn]; if(legendColumn.HeaderText.Length > 0) { // Note that extra "I" character added to add more horizontal spacing Size headerTextSize = graph.MeasureStringAbs(legendColumn.HeaderText + "I", legendColumn.HeaderFont); if(headerTextSize.Width > subColumnSizes[currentColumn, currentSubColumn]) { // Set new width subColumnSizes[currentColumn, currentSubColumn] = headerTextSize.Width; // Check for column maximum/minimum cell width restrictions if(legendColumn.MinimumWidth >= 0) { subColumnSizes[currentColumn, currentSubColumn] = (int)Math.Max(subColumnSizes[currentColumn, currentSubColumn], legendColumn.MinimumWidth * singleWCharacterSize.Width / 100f); } if(legendColumn.MaximumWidth >= 0) { subColumnSizes[currentColumn, currentSubColumn] = (int)Math.Min(subColumnSizes[currentColumn, currentSubColumn], legendColumn.MaximumWidth * singleWCharacterSize.Width / 100f); } } } } } } //************************************************************************* //** Adjust width of the cells to fit cell content displayed across //** several cells (CellSpanning). //************************************************************************* if(secondIterationRequired) { for(currentColumn = 0; currentColumn < numberOfColumns; currentColumn++) { for(int currentSubColumn = 0; currentSubColumn < numberOfSubColumns; currentSubColumn++) { for(currentRow = 0; currentRow < numberOfRowsPerColumn[currentColumn]; currentRow++) { // Get current cell size int cellWidth = cellWidths[currentColumn, currentRow, currentSubColumn]; // Second iteration used to adjust width of the cells that are used to // draw content across several horizontal cells (CellSpanning) // Check if current cell will be spanned to the next ones int cellSpan = 0; while(currentSubColumn + cellSpan + 1 < numberOfSubColumns) { int nextCellWidth = cellWidths[currentColumn, currentRow, currentSubColumn + cellSpan + 1]; if(nextCellWidth >= 0) { break; } ++cellSpan; } // Cell span was detected if(cellSpan > 0) { // Calculate total width of current cell and all overlapped cells int spanWidth = 0; for(int index = 0; index <= cellSpan; index++) { spanWidth += subColumnSizes[currentColumn, currentSubColumn + index]; } // Check if current cell fits into the cell span if(cellWidth > spanWidth) { // Adjust last span cell width to fit all curent cell content subColumnSizes[currentColumn, currentSubColumn + cellSpan] += cellWidth - spanWidth; } } } } } } //************************************************************************* //** Check if equally spaced legend columns are used //************************************************************************* if(this.IsEquallySpacedItems) { // Makre sure that same sub-colimn width are used in all columns for(int currentSubColumn = 0; currentSubColumn < numberOfSubColumns; currentSubColumn++) { int width = 0; for(currentColumn = 0; currentColumn < numberOfColumns; currentColumn++) { width = Math.Max(width, subColumnSizes[currentColumn, currentSubColumn]); } // Set new sub-column width for each column for(currentColumn = 0; currentColumn < numberOfColumns; currentColumn++) { subColumnSizes[currentColumn, currentSubColumn] = width; } } } //************************************************************************* //** Calculate total width and height occupied by all cells //************************************************************************* int totalWidth = 0; int totalTableColumnSpacingWidth = 0; for(currentColumn = 0; currentColumn < numberOfColumns; currentColumn++) { // Add up all sub-columns for(int currentSubColumn = 0; currentSubColumn < numberOfSubColumns; currentSubColumn++) { totalWidth += subColumnSizes[currentColumn, currentSubColumn]; } // Add spacer between columns if(currentColumn < numberOfColumns - 1) { totalTableColumnSpacingWidth += this.GetSeparatorSize(this.ItemColumnSeparator).Width; } } int totalHeight = 0; for(currentColumn = 0; currentColumn < numberOfColumns; currentColumn++) { int columnHeight = 0; for(currentRow = 0; currentRow < numberOfRowsPerColumn[currentColumn]; currentRow++) { columnHeight += cellHeights[currentColumn, currentRow]; } totalHeight = Math.Max(totalHeight, columnHeight); } //************************************************************************* //** Check if everything fits //************************************************************************* horizontalSpaceLeft = legendItemsAreaSize.Width - totalWidth - totalTableColumnSpacingWidth; if(horizontalSpaceLeft < 0) { fitFlag = false; } verticalSpaceLeft = legendItemsAreaSize.Height - totalHeight; if(verticalSpaceLeft < 0) { fitFlag = false; } return fitFlag; } /// /// Gets maximum number of legend cells defined as Column objects /// or Cells in the custom legend items. /// /// Maximum number of cells. private int GetNumberOfCells() { // Calculate cell number if it was not previously cached if(this._numberOfCells < 0) { // Initialize with number of defined columns this._numberOfCells = this.CellColumns.Count; // Check if number of cells in legend items exceed number of defined columns foreach(LegendItem legendItem in this.legendItems) { this._numberOfCells = Math.Max(this._numberOfCells, legendItem.Cells.Count); } } return this._numberOfCells; } #endregion // Legend Items Fitting Methods #region Legend items collection filling methods /// /// Add all series legend into items collection and then /// add custom legend items. /// private void FillLegendItemsCollection() { // Clear all items legendItems.Clear(); // Check that there is no invalid legend names in the series foreach(Series series in this.Common.DataManager.Series) { if (this.Common.ChartPicture.Legends.IndexOf(series.Legend)<0) { throw (new InvalidOperationException(SR.ExceptionLegendReferencedInSeriesNotFound(series.Name, series.Legend))); } } // Flag which indicates that series requires legend items to be reversed bool seriesWithReversedLegendItemsPresent = false; // Add legend items based on the exsisting chart series foreach(Series series in this.Common.DataManager.Series) { // Check if series uses this legend // VSTS issue #140694 fix: support of series.Legend = "Default"; if (this.Common.ChartPicture.Legends[series.Legend] != this) { continue; } // Make sure series is assigned to the chart area if(series.ChartArea.Length > 0) { // Check if chart area name is valid bool areaNameFound = false; foreach(ChartArea area in this.Common.ChartPicture.ChartAreas) { if(area.Name == series.ChartArea) { areaNameFound = true; break; } } // Check if series is visible and valid chart area name was used if(series.IsVisible() && areaNameFound) { // Check if we should add all data points into the legend IChartType chartType = this.Common.ChartTypeRegistry.GetChartType(series.ChartTypeName); // Check if series legend items should be reversed if (this.LegendItemOrder == LegendItemOrder.Auto) { if(series.ChartType == SeriesChartType.StackedArea || series.ChartType == SeriesChartType.StackedArea100 || series.ChartType == SeriesChartType.Pyramid || series.ChartType == SeriesChartType.StackedColumn || series.ChartType == SeriesChartType.StackedColumn100 ) { seriesWithReversedLegendItemsPresent = true; } } // Add item(s) based on series points label and fore color if(chartType.DataPointsInLegend) { // Check if data points have X values set bool xValuesSet = false; foreach(DataPoint point in series.Points) { if(point.XValue != 0.0) { xValuesSet = true; break; } } // Add legend items for each point int index = 0; foreach(DataPoint point in series.Points) { // Do not show empty data points in the legend if(point.IsEmpty) { ++index; continue; } // Point should not be shown in the legend if(!point.IsVisibleInLegend) { ++index; continue; } // Create new legend item LegendItem item = new LegendItem(point.Label, point.Color, ""); // Check if series is drawn in 3D chart area bool area3D = this.Common.Chart.ChartAreas[series.ChartArea].Area3DStyle.Enable3D; // Set legend item appearance properties item.SetAttributes(this.Common, series); item.SetAttributes(point, area3D); // Set chart image map properties item.ToolTip = point.ReplaceKeywords(point.LegendToolTip); item.Name = point.ReplaceKeywords(point.LegendText); item.SeriesPointIndex = index++; if(item.Name.Length == 0) { item.Name = point.ReplaceKeywords((point.Label.Length > 0) ? point.Label : point.AxisLabel); } // If legend item name is not defined - try using the X value if(item.Name.Length == 0 && xValuesSet) { item.Name = ValueConverter.FormatValue( series.Chart, this, this.Tag, point.XValue, "", // Do not use point label format! For Y values only! point.LabelFormat, point.series.XValueType, ChartElementType.LegendItem); } // If legend item name is not defined - use index if(item.Name.Length == 0) { item.Name = "Point " + index; } // Add legend item cells based on predefined columns item.AddAutomaticCells(this); foreach(LegendCell cell in item.Cells) { if(cell.Text.Length > 0) { // #LEGENDTEXT - series name cell.Text = cell.Text.Replace(KeywordName.LegendText, item.Name); // Process rest of the keywords cell.Text = point.ReplaceKeywords(cell.Text); cell.ToolTip = point.ReplaceKeywords(cell.ToolTip); } } legendItems.Add(item); } } // Add item based on series name and fore color else { // Point should not be shown in the legend if(!series.IsVisibleInLegend) { continue; } // Create legend item LegendItem item = new LegendItem(series.Name, series.Color, ""); item.SetAttributes(this.Common, series); item.ToolTip = series.ReplaceKeywords(series.LegendToolTip); if (series.LegendText.Length > 0) { item.Name = series.ReplaceKeywords(series.LegendText); } // Add legend item cells based on predefined columns item.AddAutomaticCells(this); foreach(LegendCell cell in item.Cells) { if(cell.Text.Length > 0) { // #LEGENDTEXT - series name cell.Text = cell.Text.Replace(KeywordName.LegendText, item.Name); // Process rest of the keywords cell.Text = series.ReplaceKeywords(cell.Text); cell.ToolTip = series.ReplaceKeywords(cell.ToolTip); } } legendItems.Add(item); } } } } // Check if series legend items should be reversed if (this.LegendItemOrder == LegendItemOrder.SameAsSeriesOrder || (this.LegendItemOrder == LegendItemOrder.Auto && seriesWithReversedLegendItemsPresent)) { // Reversed series generated legend items legendItems.Reverse(); } // Add custom items foreach(LegendItem item in this._customLegends) { if(item.Enabled) { legendItems.Add(item); } } // Legend can't be empty at design time if(legendItems.Count == 0 && this.Common != null && this.Common.Chart != null) { if(this.Common.Chart.IsDesignMode()) { LegendItem item = new LegendItem(this.Name + " - " + SR.DescriptionTypeEmpty, Color.White, ""); item.ImageStyle = LegendImageStyle.Line; legendItems.Add(item); } } // Add legend item cells based on predefined columns foreach(LegendItem item in this.legendItems) { item.AddAutomaticCells(this); } } #endregion #region Legend painting methods /// /// Paints legend using chart graphics object. /// /// The graph provides drawing object to the display device. A Graphics object is associated with a specific device context. internal void Paint(ChartGraphics chartGraph ) { // Reset some values this._offset = Size.Empty; this._itemColumns = 0; this._horizontalSpaceLeft = 0; this._verticalSpaceLeft = 0; this._subColumnSizes = null; this._numberOfRowsPerColumn = null; this._cellHeights = null; this.autofitFont = null; this._autoFitFontSizeAdjustment = 0; this._numberOfCells = -1; this._numberOfLegendItemsToProcess = -1; // Do nothing if legend disabled if(!this.IsEnabled() || (this.MaximumAutoSize == 0f && this.Position.Auto)) { return; } // Add all series legend into items collection and then add custom legend items FillLegendItemsCollection(); // Clear all legend item cells information foreach(LegendItem legendItem in this.legendItems) { foreach(LegendCell cell in legendItem.Cells) { cell.ResetCache(); } } // Call a notification event, so that legend items collection can be modified by user this.Common.Chart.CallOnCustomizeLegend(legendItems, this.Name); // Check if legend is empty if(this.legendItems.Count == 0) { return; } //*********************************************************** //** RecalculateAxesScale legend information //*********************************************************** this.RecalcLegendInfo(chartGraph); //*********************************************************** //** Paint legend //*********************************************************** // Call BackPaint event if( Common.ProcessModePaint ) { // Draw legend background, border and shadow chartGraph.FillRectangleRel( chartGraph.GetRelativeRectangle(Rectangle.Round(chartGraph.GetAbsoluteRectangle(this.Position.ToRectangleF()))), BackColor, BackHatchStyle, BackImage, BackImageWrapMode, BackImageTransparentColor, BackImageAlignment, BackGradientStyle, BackSecondaryColor, BorderColor, this.GetBorderSize(), BorderDashStyle, ShadowColor, ShadowOffset, PenAlignment.Inset); Common.Chart.CallOnPrePaint(new ChartPaintEventArgs(this, chartGraph, Common, Position)); } if( Common.ProcessModeRegions ) { SelectLegendBackground(); } //*********************************************************** //** Draw legend header //*********************************************************** this.DrawLegendHeader(chartGraph); //*********************************************************** //** Draw legend title //*********************************************************** this.DrawLegendTitle(chartGraph); // Add legend title hot region if( Common.ProcessModeRegions && !this._titlePosition.IsEmpty) { Common.HotRegionsList.AddHotRegion(chartGraph.GetRelativeRectangle(this._titlePosition), this, ChartElementType.LegendTitle, true ); } //*********************************************************** //** Draw legend items //*********************************************************** if(this._numberOfLegendItemsToProcess < 0) { this._numberOfLegendItemsToProcess = this.legendItems.Count; } for(int itemIndex = 0; itemIndex < this._numberOfLegendItemsToProcess; itemIndex++) { LegendItem legendItem = this.legendItems[itemIndex]; // Iterate through legend item cells for(int cellIndex = 0; cellIndex < legendItem.Cells.Count; cellIndex++) { // Get legend cell LegendCell legendCell = legendItem.Cells[cellIndex]; // Paint cell legendCell.Paint( chartGraph, this._autoFitFontSizeAdjustment, this.autofitFont, this.singleWCharacterSize); } // Paint legend item separator if(legendItem.SeparatorType != LegendSeparatorStyle.None && legendItem.Cells.Count > 0) { // Calculate separator position Rectangle separatorPosition = Rectangle.Empty; separatorPosition.X = legendItem.Cells[0].cellPosition.Left; // Find right most cell position excluding ovelapped cells that have negative size int right = 0; for(int index = legendItem.Cells.Count - 1; index >= 0; index--) { right = legendItem.Cells[index].cellPosition.Right; if(right > 0) { break; } } separatorPosition.Width = right - separatorPosition.X; separatorPosition.Y = legendItem.Cells[0].cellPosition.Bottom; separatorPosition.Height = this.GetSeparatorSize(legendItem.SeparatorType).Height; separatorPosition.Intersect(this._legendItemsAreaPosition); // Draw separator this.DrawSeparator( chartGraph, legendItem.SeparatorType, legendItem.SeparatorColor, true, separatorPosition); } } //*********************************************************** //** If legend items are in multiple columns draw vertical //** separator //*********************************************************** if(this.ItemColumnSeparator != LegendSeparatorStyle.None) { Rectangle separatorRect = Rectangle.Round(chartGraph.GetAbsoluteRectangle(this.Position.ToRectangleF())); separatorRect.Y += this.GetBorderSize() + this._titlePosition.Height; separatorRect.Height -= 2 * this.GetBorderSize() + this._titlePosition.Height; separatorRect.X += this.GetBorderSize() + this._offset.Width; separatorRect.Width = this.GetSeparatorSize(this.ItemColumnSeparator).Width; if(this._horizontalSpaceLeft > 0) { separatorRect.X += this._horizontalSpaceLeft / 2; } // Check position if(separatorRect.Width > 0 && separatorRect.Height > 0) { // Iterate through all columns for(int columnIndex = 0; columnIndex < this._itemColumns; columnIndex++ ) { // Iterate through all sub-columns int cellCount = this.GetNumberOfCells(); for(int subColumnIndex = 0; subColumnIndex < cellCount; subColumnIndex++ ) { separatorRect.X += this._subColumnSizes[columnIndex, subColumnIndex]; } // Draw separator if not the last column if(columnIndex < this._itemColumns - 1) { this.DrawSeparator(chartGraph, this.ItemColumnSeparator, this.ItemColumnSeparatorColor, false, separatorRect); } // Add separator width separatorRect.X += separatorRect.Width; } } } //*********************************************************** //** Draw special indicator on the bottom of the legend if //** it was truncated. //*********************************************************** if(this._legendItemsTruncated && this._legendItemsAreaPosition.Height > this._truncatedDotsSize / 2) { // Calculate dots step (no more than 10 pixel) int markerCount = 3; int step = (this._legendItemsAreaPosition.Width / 3) / markerCount; step = (int)Math.Min(step, 10); // Calculate start point PointF point = new PointF( this._legendItemsAreaPosition.X + this._legendItemsAreaPosition.Width / 2 - step * (float)Math.Floor(markerCount/2f), this._legendItemsAreaPosition.Bottom + (this._truncatedDotsSize + this._offset.Height) / 2); // Draw several dots at the bottom of the legend for(int index = 0; index < markerCount; index++) { chartGraph.DrawMarkerRel( chartGraph.GetRelativePoint(point), MarkerStyle.Circle, this._truncatedDotsSize, this.ForeColor, Color.Empty, 0, string.Empty, Color.Empty, 0, Color.Empty, RectangleF.Empty); // Shift to the right point.X += step; } } // Call Paint event if( Common.ProcessModePaint ) { Common.Chart.CallOnPostPaint(new ChartPaintEventArgs(this, chartGraph, Common, Position)); } // Remove temporary cells from legend items foreach(LegendItem legendItem in this.legendItems) { if(legendItem.clearTempCells) { legendItem.clearTempCells = false; legendItem.Cells.Clear(); } } } #endregion #region Legend properties /// /// Gets or sets the name of the legend. /// [ SRCategory("CategoryAttributeMisc"), Bindable(true), SRDescription("DescriptionAttributeLegend_Name"), NotifyParentPropertyAttribute(true) ] public override string Name { get { return base.Name; } set { base.Name = value; CallOnModifing(); } } /// /// Gets or sets the name of the chart area where the legend /// should be docked. /// [ SRCategory("CategoryAttributeDocking"), Bindable(true), DefaultValue(Constants.NotSetValue), SRDescription("DescriptionAttributeLegend_DockToChartArea"), TypeConverter(typeof(LegendAreaNameConverter)), NotifyParentPropertyAttribute(true) ] public string DockedToChartArea { get { return _dockedToChartArea; } set { if(value != _dockedToChartArea) { if (String.IsNullOrEmpty(value)) { _dockedToChartArea = Constants.NotSetValue; } else { if (Chart != null && Chart.ChartAreas != null) { Chart.ChartAreas.VerifyNameReference(value); } _dockedToChartArea = value; } this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets a property which indicates whether /// the legend is docked inside the chart area. /// This property is only available when DockedToChartArea is set. /// [ SRCategory("CategoryAttributeDocking"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeLegend_DockInsideChartArea"), NotifyParentPropertyAttribute(true) ] public bool IsDockedInsideChartArea { get { return _isDockedInsideChartArea; } set { if(value != _isDockedInsideChartArea) { _isDockedInsideChartArea = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the position of the legend. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), SRDescription("DescriptionAttributeLegend_Position"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ElementPositionConverter)), SerializationVisibilityAttribute(SerializationVisibility.Element) ] public ElementPosition Position { get { // Serialize only position values if Auto set to false if (this.Common != null && this.Common.Chart != null && this.Common.Chart.serializationStatus == SerializationStatus.Saving) { if(_position.Auto) { return new ElementPosition(); } else { ElementPosition newPosition = new ElementPosition(); newPosition.Auto = false; newPosition.SetPositionNoAuto(_position.X, _position.Y, _position.Width, _position.Height); return newPosition; } } return _position; } set { _position = value; this.Invalidate(false); CallOnModifing(); } } /// /// Determoines if this position should be serialized. /// /// internal bool ShouldSerializePosition() { return !this.Position.Auto; } /// /// Gets or sets a property which indicates whether /// all legend items are equally spaced. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(false), SRDescription("DescriptionAttributeLegend_EquallySpacedItems"), NotifyParentPropertyAttribute(true) ] public bool IsEquallySpacedItems { get { return _isEquallySpacedItems; } set { _isEquallySpacedItems = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets a flag which indicates whether the legend is enabled. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeLegend_Enabled"), NotifyParentPropertyAttribute(true), ParenthesizePropertyNameAttribute(true) ] public bool Enabled { get { return _enabled; } set { _enabled = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets a value that indicates if legend text is automatically sized. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeLegend_AutoFitText"), NotifyParentPropertyAttribute(true) ] public bool IsTextAutoFit { get { return _isTextAutoFit; } set { _isTextAutoFit = value; if(_isTextAutoFit) { // Reset the font size to "8" // Use current font family name ans style if possible. if(_font != null) { _font = _fontCache.GetFont(_font.FontFamily, 8, _font.Style); ; } else { _font = _fontCache.DefaultFont; } } this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the legend style. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(LegendStyle.Table), SRDescription("DescriptionAttributeLegend_LegendStyle"), NotifyParentPropertyAttribute(true), ParenthesizePropertyNameAttribute(true) ] public LegendStyle LegendStyle { get { return _legendStyle; } set { _legendStyle = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the minimum font size that can be used by the legend text's auto-fitting algorithm. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(7), SRDescription("DescriptionAttributeLegend_AutoFitMinFontSize"), ] public int AutoFitMinFontSize { get { return this._autoFitMinFontSize; } set { // Font size cannot be less than 5 if(value < 5) { throw (new InvalidOperationException(SR.ExceptionLegendAutoFitMinFontSizeInvalid)); } this._autoFitMinFontSize = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the maximum size (in percentage) of the legend used in the automatic layout algorithm. /// /// /// If the legend is docked to the left or right, this property determines the maximum width of the legend, measured as a percentage. /// If the legend is docked to the top or bottom, this property determines the maximum height of the legend, measured as a percentage. /// [ SRCategory("CategoryAttributeDocking"), DefaultValue(50f), SRDescription("DescriptionAttributeLegend_MaxAutoSize"), ] public float MaximumAutoSize { get { return this._maximumLegendAutoSize; } set { if(value < 0f || value > 100f) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionLegendMaximumAutoSizeInvalid)); } this._maximumLegendAutoSize = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets a collection of legend columns. /// [ SRCategory("CategoryAttributeCellColumns"), SRDescription("DescriptionAttributeLegend_CellColumns"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), #if DESIGNER Editor(typeof(LegendCellColumnCollectionEditor), typeof(UITypeEditor)), #endif ] public LegendCellColumnCollection CellColumns { get { return this._cellColumns; } } /// /// Gets the legend table style. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(LegendTableStyle.Auto), SRDescription("DescriptionAttributeLegend_TableStyle"), NotifyParentPropertyAttribute(true), ParenthesizePropertyNameAttribute(true) ] public LegendTableStyle TableStyle { get { return this._legendTableStyle; } set { this._legendTableStyle = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets the legend header separator style. /// [ SRCategory("CategoryAttributeCellColumns"), DefaultValue(typeof(LegendSeparatorStyle), "None"), SRDescription("DescriptionAttributeLegend_HeaderSeparator"), ] public LegendSeparatorStyle HeaderSeparator { get { return this._headerSeparator; } set { if(value != this._headerSeparator) { this._headerSeparator = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the color of the legend header separator. /// [ SRCategory("CategoryAttributeCellColumns"), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeLegend_HeaderSeparatorColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color HeaderSeparatorColor { get { return this._headerSeparatorColor; } set { if(value != this._headerSeparatorColor) { this._headerSeparatorColor = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the separator style of the legend table columns. /// [ SRCategory("CategoryAttributeCellColumns"), DefaultValue(typeof(LegendSeparatorStyle), "None"), SRDescription("DescriptionAttributeLegend_ItemColumnSeparator"), ] public LegendSeparatorStyle ItemColumnSeparator { get { return this._itemColumnSeparator; } set { if(value != this._itemColumnSeparator) { this._itemColumnSeparator = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the color of the separator of the legend table columns. /// [ SRCategory("CategoryAttributeCellColumns"), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeLegend_ItemColumnSeparatorColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color ItemColumnSeparatorColor { get { return this._itemColumnSeparatorColor; } set { if(value != this._itemColumnSeparatorColor) { this._itemColumnSeparatorColor = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the legend table column spacing, as a percentage of the legend text font. /// [ SRCategory("CategoryAttributeCellColumns"), DefaultValue(50), SRDescription("DescriptionAttributeLegend_ItemColumnSpacing"), ] public int ItemColumnSpacing { get { return this._itemColumnSpacing; } set { if(value != this._itemColumnSpacing) { if(value < 0) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionLegendColumnSpacingInvalid)); } this._itemColumnSpacing = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the legend background color. /// [ DefaultValue(typeof(Color), ""), SRCategory("CategoryAttributeAppearance"), Bindable(true), SRDescription("DescriptionAttributeBackColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color BackColor { get { return _backColor; } set { _backColor = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the legend border color. /// [ DefaultValue(typeof(Color), ""), SRCategory("CategoryAttributeAppearance"), Bindable(true), SRDescription("DescriptionAttributeBorderColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color BorderColor { get { return _borderColor; } set { _borderColor = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the legend border style. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(ChartDashStyle.Solid), SRDescription("DescriptionAttributeBorderDashStyle"), NotifyParentPropertyAttribute(true) ] public ChartDashStyle BorderDashStyle { get { return _borderDashStyle; } set { _borderDashStyle = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the legend border width. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(1), SRDescription("DescriptionAttributeBorderWidth"), NotifyParentPropertyAttribute(true) ] public int BorderWidth { get { return _borderWidth; } set { if(value < 0) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionLegendBorderWidthIsNegative)); } _borderWidth = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the legend background image. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(""), SRDescription("DescriptionAttributeBackImage"), #if DESIGNER Editor(typeof(ImageValueEditor), typeof(UITypeEditor)), #endif NotifyParentPropertyAttribute(true) ] public string BackImage { get { return _backImage; } set { _backImage = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the legend background image drawing mode. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(ChartImageWrapMode.Tile), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeImageWrapMode") ] public ChartImageWrapMode BackImageWrapMode { get { return _backImageWrapMode; } set { _backImageWrapMode = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets a color which will be replaced with a transparent color while drawing the background image. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), ""), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeImageTransparentColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color BackImageTransparentColor { get { return _backImageTransparentColor; } set { _backImageTransparentColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the background image alignment used for the unscaled drawing mode. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(ChartImageAlignmentStyle.TopLeft), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeBackImageAlign") ] public ChartImageAlignmentStyle BackImageAlignment { get { return _backImageAlignment; } set { _backImageAlignment = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets background gradient style of the legend. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(GradientStyle.None), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeBackGradientStyle"), #if DESIGNER Editor(typeof(GradientEditor), typeof(UITypeEditor)) #endif ] public GradientStyle BackGradientStyle { get { return _backGradientStyle; } set { _backGradientStyle = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the secondary background color. /// /// /// /// /// /// A value used for the secondary color of background with /// hatching or gradient fill. /// /// /// This color is used with when or /// are used. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), ""), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeBackSecondaryColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color BackSecondaryColor { get { return _backSecondaryColor; } set { _backSecondaryColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the background hatch style. /// /// /// /// /// /// A value used for the background. /// /// /// Two colors are used to draw the hatching, and . /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(ChartHatchStyle.None), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeBackHatchStyle"), #if DESIGNER Editor(typeof(HatchStyleEditor), typeof(UITypeEditor)) #endif ] public ChartHatchStyle BackHatchStyle { get { return _backHatchStyle; } set { _backHatchStyle = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the font of the legend text. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Font), "Microsoft Sans Serif, 8pt"), SRDescription("DescriptionAttributeLegend_Font"), NotifyParentPropertyAttribute(true) ] public Font Font { get { return _font; } set { this.IsTextAutoFit = false; _font = value; this.Invalidate(false); } } /// /// Gets or sets the color of the legend text. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeLegendFontColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color ForeColor { get { return _foreColor; } set { _foreColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the text alignment. /// [ SRCategory("CategoryAttributeDocking"), Bindable(true), DefaultValue(StringAlignment.Near), SRDescription("DescriptionAttributeLegend_Alignment"), NotifyParentPropertyAttribute(true) ] public StringAlignment Alignment { get { return _legendAlignment; } set { _legendAlignment = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the property that specifies where the legend docks. /// [ SRCategory("CategoryAttributeDocking"), Bindable(true), DefaultValue(Docking.Right), SRDescription("DescriptionAttributeLegend_Docking"), NotifyParentPropertyAttribute(true) ] public Docking Docking { get { return _legendDocking; } set { _legendDocking = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the offset between the legend and its shadow. /// /// /// /// An integer value that represents the offset between the legend and its shadow. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(0), SRDescription("DescriptionAttributeShadowOffset"), NotifyParentPropertyAttribute(true) ] public int ShadowOffset { get { return _shadowOffset; } set { _shadowOffset = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the color of a legend's shadow. /// /// /// /// A value used to draw a legend's shadow. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), "128, 0, 0, 0"), SRDescription("DescriptionAttributeShadowColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color ShadowColor { get { return _shadowColor; } set { _shadowColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the name of the chart area name inside which the legend is drawn. /// [ SRCategory("CategoryAttributeAppearance"), Browsable(false), Bindable(false), DefaultValue(Constants.NotSetValue), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeLegend_InsideChartArea"), EditorBrowsableAttribute(EditorBrowsableState.Never), DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Content), SerializationVisibilityAttribute(SerializationVisibility.Hidden), TypeConverter(typeof(LegendAreaNameConverter)) ] public string InsideChartArea { get { if(this.Common != null && this.Common.Chart != null && this.Common.Chart.serializing) { return "NotSet"; } return this.DockedToChartArea; } set { if(value.Length == 0) { this.DockedToChartArea = Constants.NotSetValue; } else { this.DockedToChartArea = value; } this.Invalidate(false); } } /// /// Gets the custom legend items. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeLegend_CustomItems"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), #if DESIGNER Editor(typeof(LegendItemCollectionEditor), typeof(UITypeEditor)) #endif ] public LegendItemsCollection CustomItems { get { return _customLegends; } } /// /// Gets or sets a property that defines the preferred number of characters in a line of the legend text. /// /// /// When legend text exceeds the value defined in the TextWrapThreshold property, it will be /// automatically wrapped on the next whitespace. Text will not be wrapped if there is no whitespace /// characters in the text. Set this property to zero to disable the feature. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(25), SRDescription("DescriptionAttributeLegend_TextWrapThreshold"), ] public int TextWrapThreshold { get { return this._textWrapThreshold; } set { if(value != this._textWrapThreshold) { if(value < 0) { throw (new ArgumentException(SR.ExceptionTextThresholdIsNegative, "value")); } this._textWrapThreshold = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets a property that specifies the order that legend items are shown. This property only affects /// legend items automatically added for the chart series and has no effect on custom legend items. /// /// /// When the LegendItemOrder property is set to Auto, the legend will automatically be reversed /// if StackedColumn, StackedColumn100, StackedArea or StackedArea100 chart types are used. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(LegendItemOrder.Auto), SRDescription("DescriptionAttributeLegend_Reversed"), ] public LegendItemOrder LegendItemOrder { get { return this._legendItemOrder; } set { if(value != this._legendItemOrder) { this._legendItemOrder = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets a flag which indicates whether /// legend rows should be drawn with interlaced background color. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(false), SRDescription("DescriptionAttributeLegend_InterlacedRows"), ] public bool InterlacedRows { get { return this._interlacedRows; } set { if(value != this._interlacedRows) { this._interlacedRows = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the legend interlaced row's background color. Only applicable if interlaced rows are used. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(typeof(Color), ""), SRDescription("DescriptionAttributeLegend_InterlacedRowsColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color InterlacedRowsColor { get { return this._interlacedRowsColor; } set { if(value != this._interlacedRowsColor) { this._interlacedRowsColor = value; this.Invalidate(false); CallOnModifing(); } } } #endregion #region Legend Title Properties /// /// Gets or sets the title text of the legend. /// [ SRCategory("CategoryAttributeTitle"), DefaultValue(""), SRDescription("DescriptionAttributeLegend_Title"), ] public string Title { get { return this._title; } set { if(value != this._title) { this._title = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the text color of the legend title. /// [ SRCategory("CategoryAttributeTitle"), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeLegend_TitleColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color TitleForeColor { get { return this._titleForeColor; } set { if(value != this._titleForeColor) { this._titleForeColor = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the background color of the legend title. /// [ SRCategory("CategoryAttributeTitle"), DefaultValue(typeof(Color), ""), SRDescription("DescriptionAttributeTitleBackColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color TitleBackColor { get { return this._titleBackColor; } set { if(value != this._titleBackColor) { this._titleBackColor = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the font of the legend title. /// [ SRCategory("CategoryAttributeTitle"), DefaultValue(typeof(Font), "Microsoft Sans Serif, 8pt, style=Bold"), SRDescription("DescriptionAttributeTitleFont"), ] public Font TitleFont { get { return this._titleFont; } set { if(value != this._titleFont) { this._titleFont = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the text alignment of the legend title. /// [ SRCategory("CategoryAttributeTitle"), DefaultValue(typeof(StringAlignment), "Center"), SRDescription("DescriptionAttributeLegend_TitleAlignment"), ] public StringAlignment TitleAlignment { get { return this._titleAlignment; } set { if(value != this._titleAlignment) { this._titleAlignment = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the separator style of the legend title. /// [ SRCategory("CategoryAttributeTitle"), DefaultValue(typeof(LegendSeparatorStyle), "None"), SRDescription("DescriptionAttributeLegend_TitleSeparator"), ] public LegendSeparatorStyle TitleSeparator { get { return this._titleSeparator; } set { if(value != this._titleSeparator) { this._titleSeparator = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the separator color of the legend title. /// [ SRCategory("CategoryAttributeTitle"), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeLegend_TitleSeparatorColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color TitleSeparatorColor { get { return this._titleSeparatorColor; } set { if(value != this._titleSeparatorColor) { this._titleSeparatorColor = value; this.Invalidate(false); CallOnModifing(); } } } #endregion // Legend Title Properties #region Legent Title and Header Helper methods /// /// Gets legend title size in relative coordinates. /// /// Chart graphics. /// Maximum possible legend title size. /// Legend yitle size. private Size GetTitleSize(ChartGraphics chartGraph, Size titleMaxSize) { Size titleSize = Size.Empty; if(this.Title.Length > 0) { // Adjust available space titleMaxSize.Width -= this.GetBorderSize() * 2 + this._offset.Width; // Measure title text size titleSize = chartGraph.MeasureStringAbs( this.Title.Replace("\\n", "\n"), this.TitleFont, titleMaxSize, StringFormat.GenericTypographic); // Add text spacing titleSize.Height += this._offset.Height; titleSize.Width += this._offset.Width; // Add space required for the title separator titleSize.Height += this.GetSeparatorSize(this.TitleSeparator).Height; } return titleSize; } /// /// Gets legend header size in relative coordinates. /// /// Chart graphics. /// Legend column to get the header for. /// Legend yitle size. private Size GetHeaderSize(ChartGraphics chartGraph, LegendCellColumn legendColumn) { Size headerSize = Size.Empty; if(legendColumn.HeaderText.Length > 0) { // Measure title text size headerSize = chartGraph.MeasureStringAbs( legendColumn.HeaderText.Replace("\\n", "\n") + "I", legendColumn.HeaderFont); // Add text spacing headerSize.Height += this._offset.Height; headerSize.Width += this._offset.Width; // Add space required for the title separator headerSize.Height += this.GetSeparatorSize(this.HeaderSeparator).Height; } return headerSize; } /// /// Draw Legend header. /// /// Chart graphics to draw the header on. private void DrawLegendHeader(ChartGraphics chartGraph) { // Check if header should be drawn if(!this._headerPosition.IsEmpty && this._headerPosition.Width > 0 && this._headerPosition.Height > 0) { int prevRightLocation = -1; bool redrawLegendBorder = false; // Get Legend position Rectangle legendPosition = Rectangle.Round(chartGraph.GetAbsoluteRectangle(this.Position.ToRectangleF())); legendPosition.Y += /*this.offset.Height + */this.GetBorderSize(); legendPosition.Height -= 2 * (this._offset.Height + this.GetBorderSize()); legendPosition.X += this.GetBorderSize(); legendPosition.Width -= 2 * this.GetBorderSize(); if(this.GetBorderSize() > 0) { ++legendPosition.Height; ++legendPosition.Width; } // Check if at least 1 column header has non-empty background color bool headerBackFill = false; for(int subColumnIndex = 0; subColumnIndex < this.CellColumns.Count; subColumnIndex++ ) { LegendCellColumn legendColumn = this.CellColumns[subColumnIndex]; if(!legendColumn.HeaderBackColor.IsEmpty) { headerBackFill = true; } } // Iterate through all columns for(int columnIndex = 0; columnIndex < this._itemColumns; columnIndex++ ) { int columnStart = 0; int columnWidth = 0; // Iterate through all sub-columns int numberOfSubColumns = this._subColumnSizes.GetLength(1); for(int subColumnIndex = 0; subColumnIndex < numberOfSubColumns; subColumnIndex++ ) { // Calculate position of the header Rectangle rect = this._headerPosition; if(_horizontalSpaceLeft > 0) { rect.X += (int)(this._horizontalSpaceLeft / 2f); } if(prevRightLocation != -1) { rect.X = prevRightLocation; } rect.Width = this._subColumnSizes[columnIndex, subColumnIndex]; prevRightLocation = rect.Right; // Remember column start position and update width if(subColumnIndex == 0) { columnStart = rect.Left; } columnWidth += rect.Width; // Make sure header position do not go outside of the legend rect.Intersect(legendPosition); if(rect.Width > 0 && rect.Height > 0) { // Define fill rectangle Rectangle fillRect = rect; // Make sure header fill riches legend top border if(this._titlePosition.Height <= 0) { fillRect.Y -= this._offset.Height; fillRect.Height += this._offset.Height; } // Stretch header fill rectangle and separators when vertical // separator are used or if there is 1 column with header background if( (this._itemColumns == 1 && headerBackFill) || this.ItemColumnSeparator != LegendSeparatorStyle.None) { // For the first cell in the first column stretch filling // to the left side of the legend if(columnIndex == 0 && subColumnIndex == 0) { int newX = legendPosition.X; columnWidth += columnStart - newX; columnStart = newX; fillRect.Width += fillRect.X - legendPosition.X; fillRect.X = newX; } // For the last cell in the last column stretch filling // to the right side of the legend if(columnIndex == (this._itemColumns - 1) && subColumnIndex == (numberOfSubColumns - 1) ) { columnWidth += legendPosition.Right - fillRect.Right + 1; fillRect.Width += legendPosition.Right - fillRect.Right + 1; } // For the first cell of any column except the first one // make sure we also fill the item column spacing if(columnIndex != 0 && subColumnIndex == 0) { columnWidth += this._itemColumnSpacingRel / 2; columnStart -= this._itemColumnSpacingRel / 2; fillRect.Width += this._itemColumnSpacingRel / 2; fillRect.X -= this._itemColumnSpacingRel / 2; } // For the last cell in all columns except the last one // make sure we also fill the item column spacing if(columnIndex != (this._itemColumns - 1) && subColumnIndex == (numberOfSubColumns - 1) ) { columnWidth += this._itemColumnSpacingRel / 2; fillRect.Width += this._itemColumnSpacingRel / 2; } } if(subColumnIndex < this.CellColumns.Count) { // Draw header background LegendCellColumn legendColumn = this.CellColumns[subColumnIndex]; if(!legendColumn.HeaderBackColor.IsEmpty) { redrawLegendBorder = true; // Fill title background if(fillRect.Right > legendPosition.Right) { fillRect.Width -= (legendPosition.Right - fillRect.Right); } if(fillRect.X < legendPosition.X) { fillRect.X += legendPosition.X - fillRect.X; fillRect.Width -= (legendPosition.X - fillRect.X); } fillRect.Intersect(legendPosition); chartGraph.FillRectangleRel( chartGraph.GetRelativeRectangle(fillRect), legendColumn.HeaderBackColor, ChartHatchStyle.None, string.Empty, ChartImageWrapMode.Tile, Color.Empty, ChartImageAlignmentStyle.Center, GradientStyle.None, Color.Empty, Color.Empty, 0, ChartDashStyle.NotSet, Color.Empty, 0, PenAlignment.Inset); } // Draw header text using(SolidBrush textBrush = new SolidBrush(legendColumn.HeaderForeColor)) { // Set text alignment using (StringFormat format = new StringFormat()) { format.Alignment = legendColumn.HeaderAlignment; format.LineAlignment = StringAlignment.Center; format.FormatFlags = StringFormatFlags.LineLimit; format.Trimming = StringTrimming.EllipsisCharacter; // Draw string using relative coordinates chartGraph.DrawStringRel( legendColumn.HeaderText, legendColumn.HeaderFont, textBrush, chartGraph.GetRelativeRectangle(rect), format); } } } } } // Draw header separator for each column Rectangle separatorRect = this._headerPosition; separatorRect.X = columnStart; separatorRect.Width = columnWidth; if(this.HeaderSeparator == LegendSeparatorStyle.Line || this.HeaderSeparator == LegendSeparatorStyle.DoubleLine) { // NOTE: For some reason a line with a single pen width is drawn 1 pixel longer than // any other line. Reduce width to solve the issue. legendPosition.Width -= 1; } separatorRect.Intersect(legendPosition); this.DrawSeparator(chartGraph, this.HeaderSeparator, this.HeaderSeparatorColor, true, separatorRect); // Add spacing between columns prevRightLocation += this.GetSeparatorSize(this.ItemColumnSeparator).Width; } // Draw legend border to solve any issues with header background overlapping if(redrawLegendBorder) { chartGraph.FillRectangleRel( chartGraph.GetRelativeRectangle(Rectangle.Round(chartGraph.GetAbsoluteRectangle(this.Position.ToRectangleF()))), Color.Transparent, ChartHatchStyle.None, string.Empty, ChartImageWrapMode.Tile, Color.Empty, ChartImageAlignmentStyle.Center, GradientStyle.None, Color.Empty, BorderColor, this.GetBorderSize(), BorderDashStyle, Color.Empty, 0, PenAlignment.Inset); } // Add legend header hot region if( Common.ProcessModeRegions && !this._headerPosition.IsEmpty) { Common.HotRegionsList.AddHotRegion(chartGraph.GetRelativeRectangle(this._headerPosition), this, ChartElementType.LegendHeader, true ); } } } /// /// Draw Legend title. /// /// Chart graphics to draw the title on. private void DrawLegendTitle(ChartGraphics chartGraph) { // Check if title text is specified and position recalculated if(this.Title.Length > 0 && !this._titlePosition.IsEmpty) { // Get Legend position Rectangle legendPosition = Rectangle.Round(chartGraph.GetAbsoluteRectangle(this.Position.ToRectangleF())); legendPosition.Y += this.GetBorderSize(); legendPosition.Height -= 2 * this.GetBorderSize(); legendPosition.X += this.GetBorderSize(); legendPosition.Width -= 2 * this.GetBorderSize(); if(this.GetBorderSize() > 0) { ++legendPosition.Height; ++legendPosition.Width; } // Draw title background if(!this.TitleBackColor.IsEmpty) { // Fill title background Rectangle fillRect = this._titlePosition; fillRect.Intersect(legendPosition); chartGraph.FillRectangleRel( chartGraph.GetRelativeRectangle(fillRect), this.TitleBackColor, ChartHatchStyle.None, string.Empty, ChartImageWrapMode.Tile, Color.Empty, ChartImageAlignmentStyle.Center, GradientStyle.None, Color.Empty, Color.Empty, 0, ChartDashStyle.NotSet, Color.Empty, 0, PenAlignment.Inset); } // Draw title text using(SolidBrush textBrush = new SolidBrush(this.TitleForeColor)) { // Set text alignment StringFormat format = new StringFormat(); format.Alignment = this.TitleAlignment; //format.LineAlignment = StringAlignment.Center; // Shift text rectangle by the top offset amount Rectangle rect = this._titlePosition; rect.Y += this._offset.Height; rect.X += this._offset.Width; rect.X += this.GetBorderSize(); rect.Width -= this.GetBorderSize() * 2 + this._offset.Width; // Draw string using relative coordinates rect.Intersect(legendPosition); chartGraph.DrawStringRel( this.Title.Replace("\\n", "\n"), this.TitleFont, textBrush, chartGraph.GetRelativeRectangle(rect), format); } // Draw title separator Rectangle separatorPosition = this._titlePosition; if(this.TitleSeparator == LegendSeparatorStyle.Line || this.TitleSeparator == LegendSeparatorStyle.DoubleLine) { // NOTE: For some reason a line with a single pen width is drawn 1 pixel longer than // any other line. Reduce width to solve the issue. legendPosition.Width -= 1; } separatorPosition.Intersect(legendPosition); this.DrawSeparator(chartGraph, this.TitleSeparator, this.TitleSeparatorColor, true, separatorPosition); // Draw legend border to solve any issues with title background overlapping if(!this.TitleBackColor.IsEmpty || this.TitleSeparator != LegendSeparatorStyle.None) { chartGraph.FillRectangleRel( chartGraph.GetRelativeRectangle(Rectangle.Round(chartGraph.GetAbsoluteRectangle(this.Position.ToRectangleF()))), Color.Transparent, ChartHatchStyle.None, string.Empty, ChartImageWrapMode.Tile, Color.Empty, ChartImageAlignmentStyle.Center, GradientStyle.None, Color.Empty, BorderColor, this.GetBorderSize(), BorderDashStyle, Color.Empty, 0, PenAlignment.Inset); } } } /// /// Gets legend separator size in pixels /// /// Separator type. /// Separator size in relative coordinates. internal Size GetSeparatorSize(LegendSeparatorStyle separatorType) { Size size = Size.Empty; if(separatorType == LegendSeparatorStyle.None) { size = Size.Empty; } else if(separatorType == LegendSeparatorStyle.Line) { size = new Size(1, 1); } else if(separatorType == LegendSeparatorStyle.DashLine) { size = new Size(1, 1); } else if(separatorType == LegendSeparatorStyle.DotLine) { size = new Size(1, 1); } else if(separatorType == LegendSeparatorStyle.ThickLine) { size = new Size(2, 2); } else if(separatorType == LegendSeparatorStyle.DoubleLine) { size = new Size(3, 3); } else if(separatorType == LegendSeparatorStyle.GradientLine) { size = new Size(1, 1); } else if(separatorType == LegendSeparatorStyle.ThickGradientLine) { size = new Size(2, 2); } else { throw (new InvalidOperationException(SR.ExceptionLegendSeparatorTypeUnknown(separatorType.ToString()))); } // For the vertical part of the separator always add additiobal spacing size.Width += this._itemColumnSpacingRel; return size; } /// /// Draws specified legend separator. /// /// Chart graphics. /// Separator type. /// Separator color. /// Flag that determines if separator is vertical or horizontal. /// Separator position. private void DrawSeparator( ChartGraphics chartGraph, LegendSeparatorStyle separatorType, Color color, bool horizontal, Rectangle position) { // Temporary disable antialiasing SmoothingMode oldSmoothingMode = chartGraph.SmoothingMode; chartGraph.SmoothingMode = SmoothingMode.None; // Get line position in absolute coordinates RectangleF rect = position; if(!horizontal) { rect.X += (int)(_itemColumnSpacingRel / 2f); rect.Width -= _itemColumnSpacingRel; } if(separatorType == LegendSeparatorStyle.Line) { if(horizontal) { // Draw horizontal line separator chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Solid, new PointF(rect.Left, rect.Bottom - 1), new PointF(rect.Right, rect.Bottom - 1) ); } else { // Draw vertical line separator chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Solid, new PointF(rect.Right - 1, rect.Top), new PointF(rect.Right - 1, rect.Bottom) ); } } else if(separatorType == LegendSeparatorStyle.DashLine) { if(horizontal) { // Draw horizontal line separator chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Dash, new PointF(rect.Left, rect.Bottom - 1), new PointF(rect.Right, rect.Bottom - 1) ); } else { // Draw vertical line separator chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Dash, new PointF(rect.Right - 1, rect.Top), new PointF(rect.Right - 1, rect.Bottom) ); } } else if(separatorType == LegendSeparatorStyle.DotLine) { if(horizontal) { // Draw horizontal line separator chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Dot, new PointF(rect.Left, rect.Bottom - 1), new PointF(rect.Right, rect.Bottom - 1) ); } else { // Draw vertical line separator chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Dot, new PointF(rect.Right - 1, rect.Top), new PointF(rect.Right - 1, rect.Bottom) ); } } else if(separatorType == LegendSeparatorStyle.ThickLine) { if(horizontal) { // Draw horizontal line separator chartGraph.DrawLineAbs( color, 2, ChartDashStyle.Solid, new PointF(rect.Left, rect.Bottom - 1f), new PointF(rect.Right, rect.Bottom - 1f) ); } else { // Draw vertical line separator chartGraph.DrawLineAbs( color, 2, ChartDashStyle.Solid, new PointF(rect.Right - 1f, rect.Top), new PointF(rect.Right - 1f, rect.Bottom) ); } } else if(separatorType == LegendSeparatorStyle.DoubleLine) { if(horizontal) { // Draw horizontal line separator chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Solid, new PointF(rect.Left, rect.Bottom - 3), new PointF(rect.Right, rect.Bottom - 3) ); chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Solid, new PointF(rect.Left, rect.Bottom - 1), new PointF(rect.Right, rect.Bottom - 1) ); } else { // Draw vertical line separator chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Solid, new PointF(rect.Right - 3, rect.Top), new PointF(rect.Right - 3, rect.Bottom) ); chartGraph.DrawLineAbs( color, 1, ChartDashStyle.Solid, new PointF(rect.Right - 1, rect.Top), new PointF(rect.Right - 1, rect.Bottom) ); } } else if(separatorType == LegendSeparatorStyle.GradientLine) { if(horizontal) { // Draw horizontal line separator chartGraph.FillRectangleAbs( new RectangleF(rect.Left, rect.Bottom - 1f, rect.Width, 0f), Color.Transparent, ChartHatchStyle.None, string.Empty, ChartImageWrapMode.Tile, Color.Empty, ChartImageAlignmentStyle.Center, GradientStyle.VerticalCenter, color, Color.Empty, 0, ChartDashStyle.NotSet, PenAlignment.Inset); } else { // Draw vertical line separator chartGraph.FillRectangleAbs( new RectangleF(rect.Right - 1f, rect.Top, 0f, rect.Height), Color.Transparent, ChartHatchStyle.None, string.Empty, ChartImageWrapMode.Tile, Color.Empty, ChartImageAlignmentStyle.Center, GradientStyle.HorizontalCenter, color, Color.Empty, 0, ChartDashStyle.NotSet, PenAlignment.Inset); } } else if(separatorType == LegendSeparatorStyle.ThickGradientLine) { if(horizontal) { // Draw horizontal line separator chartGraph.FillRectangleAbs( new RectangleF(rect.Left, rect.Bottom - 2f, rect.Width, 1f), Color.Transparent, ChartHatchStyle.None, string.Empty, ChartImageWrapMode.Tile, Color.Empty, ChartImageAlignmentStyle.Center, GradientStyle.VerticalCenter, color, Color.Empty, 0, ChartDashStyle.NotSet, PenAlignment.Inset); } else { // Draw vertical line separator chartGraph.FillRectangleAbs( new RectangleF(rect.Right - 2f, rect.Top, 1f, rect.Height), Color.Transparent, ChartHatchStyle.None, string.Empty, ChartImageWrapMode.Tile, Color.Empty, ChartImageAlignmentStyle.Center, GradientStyle.HorizontalCenter, color, Color.Empty, 0, ChartDashStyle.NotSet, PenAlignment.Inset); } } // Restore smoothing chartGraph.SmoothingMode = oldSmoothingMode; } #endregion // Legent Title Helper methods #region Helper methods /// /// Get visible legend border size. /// /// Visible legend border size. private int GetBorderSize() { if(this.BorderWidth > 0 && this.BorderDashStyle != ChartDashStyle.NotSet && !this.BorderColor.IsEmpty && this.BorderColor != Color.Transparent) { return this.BorderWidth; } return 0; } /// /// Helper method which returns current legend table style. /// /// Chart graphics. /// Legend table style. private LegendTableStyle GetLegendTableStyle(ChartGraphics chartGraph) { LegendTableStyle style = this.TableStyle; if(this.TableStyle == LegendTableStyle.Auto) { if(this.Position.Auto) { // If legend is automatically positioned, use docking // do determine preffered table style if(this.Docking == Docking.Left || this.Docking == Docking.Right) { return LegendTableStyle.Tall; } else { return LegendTableStyle.Wide; } } else { // If legend is custom positioned, use legend width and heiht // to determine the best table layout. SizeF legendPixelSize = chartGraph.GetAbsoluteRectangle(this.Position.ToRectangleF()).Size; if(legendPixelSize.Width < legendPixelSize.Height) { return LegendTableStyle.Tall; } else { return LegendTableStyle.Wide; } } } return style; } /// /// Helper method that checks if legend is enabled. /// /// True if legend is enabled. internal bool IsEnabled() { if(this.Enabled) { // Check if legend is docked to the chart area if(this.DockedToChartArea.Length > 0 && this.Common != null && this.Common.ChartPicture != null) { if(this.Common.ChartPicture.ChartAreas.IndexOf(this.DockedToChartArea) >= 0) { // Do not show legend when it is docked to invisible chart area ChartArea area = this.Common.ChartPicture.ChartAreas[this.DockedToChartArea]; if(!area.Visible) { return false; } } } return true; } return false; } /// /// Invalidate chart legend when one of the properties is changed /// /// Indicates that only legend area should be invalidated. [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "This parameter is used when compiling for the WinForms version of Chart")] internal void Invalidate(bool invalidateLegendOnly) { if(Chart != null && !Chart.disableInvalidates) { if(invalidateLegendOnly) { // Calculate the position of the legend Rectangle invalRect = Chart.ClientRectangle; if(this.Position.Width != 0 && this.Position.Height != 0 ) { // Convert relative coordinates to absolute coordinates invalRect.X = (int)(this.Position.X * (this.Common.ChartPicture.Width - 1) / 100F); invalRect.Y = (int)(this.Position.Y * (this.Common.ChartPicture.Height - 1) / 100F); invalRect.Width = (int)(this.Position.Width * (this.Common.ChartPicture.Width - 1) / 100F); invalRect.Height = (int)(this.Position.Height * (this.Common.ChartPicture.Height - 1) / 100F); // Inflate rectangle size using border size and shadow size invalRect.Inflate(this.BorderWidth + this.ShadowOffset + 1, this.BorderWidth + this.ShadowOffset + 1); } // Invalidate legend rectangle only Chart.dirtyFlag = true; Chart.Invalidate(invalRect); } else { Invalidate(); } } } #endregion #region IDisposable Members /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (disposing) { //Free managed resources if (_fontCache != null) { _fontCache.Dispose(); _fontCache = null; } if (legendItems != null) { legendItems.Dispose(); legendItems = null; } if (_cellColumns != null) { _cellColumns.Dispose(); _cellColumns = null; } if (_customLegends != null) { _customLegends.Dispose(); _customLegends = null; } if (_position != null) { _position.Dispose(); _position = null; } } } #endregion } /// /// The LegendCollection class is a strongly typed collection of legends. /// [ SRDescription("DescriptionAttributeLegendCollection_LegendCollection"), ] public class LegendCollection : ChartNamedElementCollection { #region Constructors /// /// LegendCollection constructor. /// /// Chart picture object. internal LegendCollection(ChartPicture chartPicture) : base(chartPicture) { } #endregion #region Properties /// /// Gets the default legend name. /// internal string DefaultNameReference { get { return this.Count > 0 ? this[0].Name : String.Empty; } } #endregion #region Methods /// /// Creates a new Legend with the specified name and adds it to the collection. /// /// The new chart area name. /// New legend public Legend Add(string name) { Legend legend = new Legend(name); this.Add(legend); return legend; } /// /// Recalculates legend position in the collection. /// /// Chart graphics used. /// Area where the legend should be positioned. /// Spacing size as a percentage of the area. internal void CalcLegendPosition( ChartGraphics chartGraph, ref RectangleF chartAreasRectangle, float elementSpacing) { // Loop through all legends foreach(Legend legend in this) { // Calculate position of the legends docked to the chart picture if(legend.IsEnabled() && legend.DockedToChartArea == Constants.NotSetValue && legend.Position.Auto) { legend.CalcLegendPosition(chartGraph, ref chartAreasRectangle, elementSpacing); } } } /// /// Recalculates legend position in the collection for legends docked outside of chart area. /// /// Chart graphics used. /// Area the legend is docked to. /// Area where the legend should be positioned. /// Spacing size as a percentage of the area. internal void CalcOutsideLegendPosition( ChartGraphics chartGraph, ChartArea area, ref RectangleF chartAreasRectangle, float elementSpacing) { if(Common != null && Common.ChartPicture != null) { // Get elemets spacing float areaSpacing = Math.Min((chartAreasRectangle.Height/100F) * elementSpacing, (chartAreasRectangle.Width/100F) * elementSpacing); // Loop through all legends foreach(Legend legend in this) { // Check if all chart area names are valid if (legend.DockedToChartArea != Constants.NotSetValue && this.Chart.ChartAreas.IndexOf(legend.DockedToChartArea)<0) { throw (new ArgumentException(SR.ExceptionLegendDockedChartAreaIsMissing((string)legend.DockedToChartArea))); } // Process only legends docked to specified area if(legend.IsEnabled() && legend.IsDockedInsideChartArea == false && legend.DockedToChartArea == area.Name && legend.Position.Auto) { // Calculate legend position legend.CalcLegendPosition(chartGraph, ref chartAreasRectangle, areaSpacing); // Adjust legend position RectangleF legendPosition = legend.Position.ToRectangleF(); if(legend.Docking == Docking.Top) { legendPosition.Y -= areaSpacing; if(!area.Position.Auto) { legendPosition.Y -= legendPosition.Height; } } else if(legend.Docking == Docking.Bottom) { legendPosition.Y += areaSpacing; if(!area.Position.Auto) { legendPosition.Y = area.Position.Bottom + areaSpacing; } } if(legend.Docking == Docking.Left) { legendPosition.X -= areaSpacing; if(!area.Position.Auto) { legendPosition.X -= legendPosition.Width; } } if(legend.Docking == Docking.Right) { legendPosition.X += areaSpacing; if(!area.Position.Auto) { legendPosition.X = area.Position.Right + areaSpacing; } } legend.Position.SetPositionNoAuto(legendPosition.X, legendPosition.Y, legendPosition.Width, legendPosition.Height); } } } } /// /// Recalculates legend position inside chart area in the collection. /// /// Chart graphics used. /// Spacing size as a percentage of the area. internal void CalcInsideLegendPosition( ChartGraphics chartGraph, float elementSpacing) { if(Common != null && Common.ChartPicture != null) { // Check if all chart area names are valid foreach(Legend legend in this) { if (legend.DockedToChartArea != Constants.NotSetValue) { try { ChartArea area = Common.ChartPicture.ChartAreas[legend.DockedToChartArea]; } catch { throw(new ArgumentException( SR.ExceptionLegendDockedChartAreaIsMissing( (string)legend.DockedToChartArea ) ) ); } } } // Loop through all chart areas foreach (ChartArea area in Common.ChartPicture.ChartAreas) { // Check if chart area is visible if(area.Visible) { // Get area position RectangleF legendPlottingRectangle = area.PlotAreaPosition.ToRectangleF(); // Get elemets spacing float areaSpacing = Math.Min((legendPlottingRectangle.Height/100F) * elementSpacing, (legendPlottingRectangle.Width/100F) * elementSpacing); // Loop through all legends foreach(Legend legend in this) { if(legend.IsEnabled() && legend.IsDockedInsideChartArea == true && legend.DockedToChartArea == area.Name && legend.Position.Auto) { // Calculate legend position legend.CalcLegendPosition(chartGraph, ref legendPlottingRectangle, areaSpacing); } } } } } } #endregion #region Event handlers internal void ChartAreaNameReferenceChanged(object sender, NameReferenceChangedEventArgs e) { //If all the chart areas are removed and then the first one is added we don't want to dock the legends if (e.OldElement == null) return; foreach (Legend legend in this) if (legend.DockedToChartArea == e.OldName) legend.DockedToChartArea = e.NewName; } #endregion } /// /// The LegendItemsCollection class is a strongly typed collection of legend items. /// [ SRDescription("DescriptionAttributeCustomLabelsCollection_CustomLabelsCollection"), ] public class LegendItemsCollection : ChartElementCollection { #region Constructors /// /// LegendItemsCollection constructor /// internal LegendItemsCollection(Legend legend) : base(legend) { } #endregion #region Methods /// /// Adds a legend item into the collection. /// /// Legend item color. /// Legend item text. /// Index of newly added item. public int Add(Color color, string text) { LegendItem item = new LegendItem(text, color, ""); Add(item); return Count - 1; } /// /// Insert a legend item into the collection. /// /// Index to insert at. /// Legend item color. /// Legend item text. /// Index of newly added item. public void Insert(int index, Color color, string text) { LegendItem item = new LegendItem(text, color, ""); this.Insert(index, item); } /// /// Adds a legend item into the collection. /// /// Legend item image. /// Legend item text. /// Index of newly added item. public int Add(string image, string text) { LegendItem item = new LegendItem(text, Color.Empty, image); Add(item); return Count-1; } /// /// Insert one legend item into the collection. /// /// Index to insert at. /// Legend item image. /// Legend item text. /// Index of newly added item. public void Insert(int index, string image, string text) { LegendItem item = new LegendItem(text, Color.Empty, image); this.Insert(index, item); } /// /// Reverses the order of items in the collection. /// public void Reverse() { List list = this.Items as List; list.Reverse(); Invalidate(); } #endregion } /// /// The LegendItem class represents a single item (row) in the legend. /// It contains properties which describe visual appearance and /// content of the legend item. /// [ SRDescription("DescriptionAttributeLegendItem_LegendItem"), DefaultProperty("Name") ] public class LegendItem : ChartNamedElement { #region Fields // Private data members, which store properties values private Color _color = Color.Empty; private string _image = ""; private string _seriesName = ""; private int _seriesPointIndex = -1; // Chart image map properties private string _toolTip = ""; // Additional appearance properties internal LegendImageStyle style = LegendImageStyle.Rectangle; internal GradientStyle backGradientStyle = GradientStyle.None; internal Color backSecondaryColor = Color.Empty; internal Color backImageTransparentColor = Color.Empty; internal Color borderColor = Color.Black; internal int borderWidth = 1; internal ChartDashStyle borderDashStyle = ChartDashStyle.Solid; internal ChartHatchStyle backHatchStyle = ChartHatchStyle.None; internal int shadowOffset = 0; internal Color shadowColor = Color.FromArgb(128, 0, 0, 0); internal ChartImageWrapMode backImageWrapMode = ChartImageWrapMode.Tile; internal ChartImageAlignmentStyle backImageAlign = ChartImageAlignmentStyle.TopLeft; // Marker properties internal MarkerStyle markerStyle = MarkerStyle.None; internal int markerSize = 5; internal string markerImage = ""; internal Color markerImageTransparentColor = Color.Empty; internal Color markerColor = Color.Empty; internal Color markerBorderColor = Color.Empty; // True if legend item is enabled. private bool _enabled = true; // Series marker border width private int _markerBorderWidth = 1; // Collection of legend item cells private LegendCellCollection _cells = null; // Legend item visual separator private LegendSeparatorStyle _separatorType = LegendSeparatorStyle.None; // Legend item visual separator color private Color _separatorColor = Color.Black; // Indicates that temporary cells where added and thet have to be removed internal bool clearTempCells = false; #endregion #region Constructors /// /// LegendItem constructor /// public LegendItem() { // Create collection of legend item cells this._cells = new LegendCellCollection(this); } /// /// LegendItem constructor /// /// Item name. /// Item color. /// Item image. public LegendItem(string name, Color color, string image) : base (name) { this._color = color; this._image = image; // Create collection of legend item cells this._cells = new LegendCellCollection(this); } #endregion #region Legend item properties /// /// Gets the Legend object which the item belongs to. /// [ Bindable(false), Browsable(false), DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden), SerializationVisibilityAttribute(SerializationVisibility.Hidden), ] public Legend Legend { get { if (Parent != null) return Parent.Parent as Legend; else return null; } } /// /// Gets or sets the name of the legend item. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), SRDescription("DescriptionAttributeLegendItem_Name"), NotifyParentPropertyAttribute(true), ParenthesizePropertyNameAttribute(true) ] public override string Name { get { return base.Name; } set { base.Name = value; CallOnModifing(); } } /// /// Gets or sets the color of the legend item. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), SRDescription("DescriptionAttributeLegendItem_Color"), DefaultValue(typeof(Color), ""), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color Color { get { return _color; } set { _color = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets a string value that represents a URL to an image file, which will be used for the legend item's symbol. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), SRDescription("DescriptionAttributeLegendItem_Image"), DefaultValue(""), #if DESIGNER Editor(typeof(ImageValueEditor), typeof(UITypeEditor)), #endif NotifyParentPropertyAttribute(true) ] public string Image { get { return _image; } set { _image = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the picture style of the legend item image. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(LegendImageStyle), "Rectangle"), SRDescription("DescriptionAttributeLegendItem_Style"), ParenthesizePropertyNameAttribute(true) ] public LegendImageStyle ImageStyle { get { return style; } set { style = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the border color of the legend item. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeBorderColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color BorderColor { get { return borderColor; } set { borderColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the background hatch style of the legend item. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(ChartHatchStyle.None), SRDescription("DescriptionAttributeBackHatchStyle"), #if DESIGNER Editor(typeof(HatchStyleEditor), typeof(UITypeEditor)) #endif ] public ChartHatchStyle BackHatchStyle { get { return backHatchStyle; } set { backHatchStyle = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets a color which will be replaced with a transparent color while drawing the background image. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), ""), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeImageTransparentColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color BackImageTransparentColor { get { return backImageTransparentColor; } set { backImageTransparentColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets background gradient style of the legend item. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(GradientStyle.None), SRDescription("DescriptionAttributeBackGradientStyle"), #if DESIGNER Editor(typeof(GradientEditor), typeof(UITypeEditor)) #endif ] public GradientStyle BackGradientStyle { get { return backGradientStyle; } set { backGradientStyle = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the secondary background color. /// /// /// /// /// /// A value used for the secondary color of background with /// hatching or gradient fill. /// /// /// This color is used with when or /// are used. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), ""), SRDescription("DescriptionAttributeBackSecondaryColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color BackSecondaryColor { get { return backSecondaryColor; } set { if(value != Color.Empty && (value.A != 255 || value == Color.Transparent)) { throw (new ArgumentException(SR.ExceptionBackSecondaryColorIsTransparent)); } backSecondaryColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the border width of the legend item. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(1), SRDescription("DescriptionAttributeBorderWidth"), ] public int BorderWidth { get { return borderWidth; } set { if(value < 0) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionBorderWidthIsZero)); } borderWidth = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets a flag which indicates whether the Legend item is enabled. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(true), SRDescription("DescriptionAttributeLegendItem_Enabled"), ParenthesizePropertyNameAttribute(true), ] public bool Enabled { get { return this._enabled; } set { this._enabled = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the marker border width of the legend item. /// [ SRCategory("CategoryAttributeMarker"), DefaultValue(1), SRDescription("DescriptionAttributeMarkerBorderWidth"), ] public int MarkerBorderWidth { get { return this._markerBorderWidth; } set { if(value < 0) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionLegendMarkerBorderWidthIsNegative)); } this._markerBorderWidth = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the legend item border style. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(ChartDashStyle.Solid), SRDescription("DescriptionAttributeBorderDashStyle"), ] public ChartDashStyle BorderDashStyle { get { return borderDashStyle; } set { borderDashStyle = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the offset between the legend item and its shadow. /// /// /// /// An integer value that represents the offset between the legend item and its shadow. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), SRDescription("DescriptionAttributeShadowOffset"), DefaultValue(0) ] public int ShadowOffset { get { return shadowOffset; } set { shadowOffset = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the color of a legend item's shadow. /// /// /// /// A value used to draw a legend item's shadow. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), "128,0,0,0"), SRDescription("DescriptionAttributeShadowColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color ShadowColor { get { return shadowColor; } set { shadowColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the marker style of the legend item. /// [ SRCategory("CategoryAttributeMarker"), Bindable(true), DefaultValue(MarkerStyle.None), SRDescription("DescriptionAttributeLegendItem_MarkerStyle"), #if DESIGNER Editor(typeof(MarkerStyleEditor), typeof(UITypeEditor)), #endif RefreshProperties(RefreshProperties.All) ] public MarkerStyle MarkerStyle { get { return markerStyle; } set { markerStyle = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the marker size of the legend item. /// [ SRCategory("CategoryAttributeMarker"), Bindable(true), DefaultValue(5), SRDescription("DescriptionAttributeLegendItem_MarkerSize"), RefreshProperties(RefreshProperties.All) ] public int MarkerSize { get { return markerSize; } set { markerSize = value; this.Invalidate(false); CallOnModifing(); } } /// /// Gets or sets the marker image of the legend item. /// [ SRCategory("CategoryAttributeMarker"), Bindable(true), DefaultValue(""), SRDescription("DescriptionAttributeMarkerImage"), #if DESIGNER Editor(typeof(ImageValueEditor), typeof(UITypeEditor)), #endif RefreshProperties(RefreshProperties.All) ] public string MarkerImage { get { return markerImage; } set { markerImage = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets a color which will be replaced with a transparent color while drawing the marker image. /// [ SRCategory("CategoryAttributeMarker"), Bindable(true), DefaultValue(typeof(Color), ""), SRDescription("DescriptionAttributeImageTransparentColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)), #endif RefreshProperties(RefreshProperties.All) ] public Color MarkerImageTransparentColor { get { return markerImageTransparentColor; } set { markerImageTransparentColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the marker color of the legend item. /// [ SRCategory("CategoryAttributeMarker"), Bindable(true), DefaultValue(typeof(Color), ""), SRDescription("DescriptionAttributeLegendItem_MarkerColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)), #endif RefreshProperties(RefreshProperties.All) ] public Color MarkerColor { get { return markerColor; } set { markerColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the marker border color of the legend item. /// [ SRCategory("CategoryAttributeMarker"), Bindable(true), DefaultValue(typeof(Color), ""), SRDescription("DescriptionAttributeMarkerBorderColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)), #endif RefreshProperties(RefreshProperties.All) ] public Color MarkerBorderColor { get { return markerBorderColor; } set { markerBorderColor = value; this.Invalidate(true); CallOnModifing(); } } /// /// Gets or sets the series name of the legend item.. /// [ Browsable(false), DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden), SerializationVisibilityAttribute(SerializationVisibility.Hidden), SRDescription("DescriptionAttributeLegendItem_SeriesName"), DefaultValue("") ] public string SeriesName { get { return _seriesName; } set { _seriesName = value; } } /// /// Gets or sets the index of the legend item's associated DataPoint object. /// [ Browsable(false), DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden), SerializationVisibilityAttribute(SerializationVisibility.Hidden), SRDescription("DescriptionAttributeLegendItem_SeriesPointIndex"), DefaultValue(-1) ] public int SeriesPointIndex { get { return _seriesPointIndex; } set { _seriesPointIndex = value; } } /// /// Gets or sets the separator style of the legend item. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(typeof(LegendSeparatorStyle), "None"), SRDescription("DescriptionAttributeLegendItem_Separator"), ] public LegendSeparatorStyle SeparatorType { get { return this._separatorType; } set { if(value != this._separatorType) { this._separatorType = value; this.Invalidate(false); CallOnModifing(); } } } /// /// Gets or sets the separator color of the legend item. /// [ SRCategory("CategoryAttributeAppearance"), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeLegendItem_SeparatorColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color SeparatorColor { get { return this._separatorColor; } set { if(value != this._separatorColor) { this._separatorColor = value; this.Invalidate(false); CallOnModifing(); } } } /// /// The LegendCellCollection class is a collection of legend item cells. /// [ SRCategory("CategoryAttributeAppearance"), SRDescription("DescriptionAttributeLegendItem_Cells"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), #if DESIGNER Editor(typeof(LegendCollectionEditor), typeof(UITypeEditor)) #endif ] public LegendCellCollection Cells { get { return this._cells; } } #endregion #region IMapAreaAttributesutes Properties implementation /// /// Tooltip of the area. /// [ SRCategory("CategoryAttributeMapArea"), Bindable(true), SRDescription("DescriptionAttributeToolTip"), DefaultValue("") ] public string ToolTip { set { _toolTip = value; if(Chart != null && Chart.selection != null) { Chart.selection.enabledChecked = false; } } get { return _toolTip; } } #endregion #region Helper methods /// /// Helper method adds default legend item cells based on the columns /// specified. If columns collection is empty we assume the presence of /// two columns: series marker and legend item text. /// /// Legend this item belongs to. internal void AddAutomaticCells(Legend legend) { // Check if cells defined if(this.Cells.Count == 0) { // Check if legend item was generated for the series if(this.SeriesName.Length > 0) { // If legend do not have any columns set add a series marker // and legend text cells if(legend.CellColumns.Count == 0) { // VSTS 96787 - Text Direction (RTL/LTR) if (legend.Common != null && legend.Common.ChartPicture.RightToLeft == RightToLeft.Yes) { this.Cells.Add(LegendCellType.Text, KeywordName.LegendText, ContentAlignment.MiddleLeft); this.Cells.Add(LegendCellType.SeriesSymbol, string.Empty, ContentAlignment.MiddleCenter); } else { this.Cells.Add(LegendCellType.SeriesSymbol, string.Empty, ContentAlignment.MiddleCenter); this.Cells.Add(LegendCellType.Text, KeywordName.LegendText, ContentAlignment.MiddleLeft); } } else { // Add cell for each of the columns foreach(LegendCellColumn legendColumn in legend.CellColumns) { this.Cells.Add(legendColumn.CreateNewCell()); } } } else { // Add Marker plus text for everything else this.clearTempCells = true; this.Cells.Add(LegendCellType.SeriesSymbol, string.Empty, ContentAlignment.MiddleCenter); this.Cells.Add(LegendCellType.Text, KeywordName.LegendText, ContentAlignment.MiddleLeft); } } } /// /// Sets legend item properties from the series /// /// Series object. /// Common elements object. internal void SetAttributes(CommonElements common, Series series) { // Get legend item picture style IChartType chartType = common.ChartTypeRegistry.GetChartType(series.ChartTypeName); style = chartType.GetLegendImageStyle(series); // Set series name _seriesName = series.Name; // Get shadow properties shadowOffset = series.ShadowOffset; shadowColor = series.ShadowColor; // Check if series is drawn in 3D chart area bool area3D = common.Chart.ChartAreas[series.ChartArea].Area3DStyle.Enable3D; // Get other properties SetAttributes((DataPointCustomProperties) series, area3D); } /// /// Sets legend item properties from the DataPointCustomProperties object. /// /// DataPointCustomProperties object. /// Element belongs to the 3D area. internal void SetAttributes(DataPointCustomProperties properties, bool area3D) { borderColor = properties.BorderColor; borderWidth = properties.BorderWidth; borderDashStyle = properties.BorderDashStyle; markerStyle = properties.MarkerStyle; markerSize = properties.MarkerSize; markerImage = properties.MarkerImage; markerImageTransparentColor = properties.MarkerImageTransparentColor; markerColor = properties.MarkerColor; markerBorderColor = properties.MarkerBorderColor; this._markerBorderWidth = properties.MarkerBorderWidth; float dpi = 96; if(Common != null) dpi = Common.graph.Graphics.DpiX; int maxBorderWidth = (int)Math.Round((2 * dpi) / 96); if (this._markerBorderWidth > maxBorderWidth) { this._markerBorderWidth = maxBorderWidth; } if(properties.MarkerBorderWidth <= 0) { markerBorderColor = Color.Transparent; } // Improve readability of the line series marker by using at least 2 pixel wide lines if(this.style == LegendImageStyle.Line && borderWidth <= (int)Math.Round(dpi / 96) ) { borderWidth = maxBorderWidth; } if(!area3D) { backGradientStyle = properties.BackGradientStyle; backSecondaryColor = properties.BackSecondaryColor; backImageTransparentColor = properties.BackImageTransparentColor; backImageWrapMode = properties.BackImageWrapMode; backImageAlign = properties.BackImageAlignment; backHatchStyle = properties.BackHatchStyle; } } /// /// Invalidate chart (or just legend )when collection is changed /// [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "This parameter is used when compiling for the WinForms version of Chart")] private void Invalidate(bool invalidateLegendOnly) { if(Legend != null) { // Invalidate control Legend.Invalidate(invalidateLegendOnly); } } #endregion #region IDisposable Members /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (disposing) { if (_cells != null) { _cells.Dispose(); _cells = null; } } base.Dispose(disposing); } #endregion } }