// 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