|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: Legend.cs
//
// Namespace: DataVisualization.Charting
//
// Classes: Legend, LegendCollection, LegendItemsCollection,
// LegendItem
//
// 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.
//
// Reviewed: AG - Jul 31, 2002;
// GS - Aug 7, 2002
// AG - Microsoft 14, 2007
//
//===================================================================
#region Used namespaces
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Diagnostics.CodeAnalysis;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting.Data;
using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
using System.Windows.Forms.DataVisualization.Charting.Utilities;
using System.Windows.Forms.DataVisualization.Charting.Borders3D;
using System.Windows.Forms.DataVisualization.Charting;
using System.ComponentModel.Design.Serialization;
using System.Reflection;
using System.Windows.Forms.Design;
#else
using System.Web;
using System.Web.UI;
using System.Web.UI.DataVisualization.Charting;
using System.Web.UI.DataVisualization.Charting.Data;
using System.Web.UI.DataVisualization.Charting.Utilities;
using System.Web.UI.DataVisualization.Charting.ChartTypes;
#endif
#endregion
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting
#else
namespace System.Web.UI.DataVisualization.Charting
#endif
{
#region Legend enumerations
/// <summary>
/// An enumeration of legend item orderings.
/// </summary>
public enum LegendItemOrder
{
/// <summary>
/// Items will be added into the legend in an order automatically determined by the chart.
/// </summary>
Auto,
/// <summary>
/// Items will be added into the legend in the same order as the chart series.
/// </summary>
SameAsSeriesOrder,
/// <summary>
/// Items will be added into the legend in the same order as the chart series.
/// </summary>
ReversedSeriesOrder
};
/// <summary>
/// An enumeration of legend separator styles.
/// </summary>
public enum LegendSeparatorStyle
{
/// <summary>
/// No separator will be shown.
/// </summary>
None,
/// <summary>
/// Single solid line separator.
/// </summary>
Line,
/// <summary>
/// Single solid thick line separator.
/// </summary>
ThickLine,
/// <summary>
/// Double solid line separator.
/// </summary>
DoubleLine,
/// <summary>
/// Single dash line separator.
/// </summary>
DashLine,
/// <summary>
/// Single dot line separator.
/// </summary>
DotLine,
/// <summary>
/// Gradient solid line separator.
/// </summary>
GradientLine,
/// <summary>
/// Thick gradient solid line separator.
/// </summary>
ThickGradientLine,
}
/// <summary>
/// An enumeration that specifies a style for a legend item's symbol.
/// </summary>
public enum LegendImageStyle
{
/// <summary>
/// The symbol will be a rectangle.
/// </summary>
Rectangle,
/// <summary>
/// The symbol will be a line.
/// </summary>
Line,
/// <summary>
/// The symbol will be a marker.
/// </summary>
Marker
}
/// <summary>
/// An enumeration of legend styles.
/// </summary>
public enum LegendStyle
{
/// <summary>
/// One column, many rows.
/// </summary>
Column,
/// <summary>
/// One row, many columns.
/// </summary>
Row,
/// <summary>
/// Many column, many rows.
/// </summary>
Table
};
/// <summary>
/// An enumeration of legend table styles.
/// </summary>
public enum LegendTableStyle
{
/// <summary>
/// The legend table style is automatically determined by the chart.
/// </summary>
Auto,
/// <summary>
/// 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.
/// </summary>
Wide,
/// <summary>
/// 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.
/// </summary>
Tall
};
#endregion
/// <summary>
/// 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.
/// </summary>
[
SRDescription("DescriptionAttributeLegend_Legend"),
DefaultProperty("Enabled"),
]
#if ASPPERM_35
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
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
/// <summary>
/// Legend constructor
/// </summary>
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;
}
/// <summary>
/// Legend constructor
/// </summary>
/// <param name="name">The legend name.</param>
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
/// <summary>
/// Recalculates legend information:
/// - legend items collection
/// - maximum text rectangle
/// </summary>
/// <param name="chartGraph">Reference to the chart graphics.</param>
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;
}
}
}
}
/// <summary>
/// Gets single cell position in relative coordinates.
/// </summary>
/// <param name="columnIndex">Cell column index.</param>
/// <param name="rowIndex">Cell row index.</param>
/// <param name="cellIndex">Index of the cell in the legend item.</param>
/// <param name="itemHalfSpacing">Half legend item spacing in relative coordinates.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Calculates the optimal size of the legend.
/// </summary>
/// <param name="chartGraph">Chart graphics object.</param>
/// <param name="maxSizeRel">Max size for the legend.</param>
/// <returns>Legend optimal size.</returns>
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);
}
/// <summary>
/// Recalculates legend position.
/// </summary>
/// <param name="chartGraph">Chart graphics used.</param>
/// <param name="chartAreasRectangle">Area where the legend should be positioned.</param>
/// <param name="elementSpacing">Spacing size as a percentage of the area.</param>
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);
}
/// <summary>
/// Get number of columns and rows that can be fit in specified size.
/// </summary>
/// <param name="chartGraph">Chart graphics.</param>
/// <param name="legendSize">Legend size.</param>
/// <param name="numberOfItemsToCheck">Number of legend items to check.</param>
/// <param name="numberOfRowsPerColumn">Array with number of rows per each column.</param>
/// <param name="columnNumber">Returns number of columns.</param>
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);
}
/// <summary>
/// Get number of columns and rows that can be fit in specified size.
/// </summary>
/// <param name="chartGraph">Chart graphics.</param>
/// <param name="legendSize">Legend size.</param>
/// <param name="numberOfItemsToCheck">Legend items number to check.</param>
/// <param name="numberOfRowsPerColumn">Array with number of rows per each column.</param>
/// <param name="columnNumber">Returns number of columns.</param>
/// <param name="horSpaceLeft">Returns horizontal spacing left.</param>
/// <param name="vertSpaceLeft">Returns vertical spacing left.</param>
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);
}
/// <summary>
/// Gets column height.
/// </summary>
/// <param name="columnIndex">Index of the column to get the height for.</param>
/// <returns>Column height in relative coordinates.</returns>
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;
}
/// <summary>
/// Checks if legend background is selected.
/// </summary>
internal void SelectLegendBackground()
{
Common.HotRegionsList.AddHotRegion(this.Position.ToRectangleF(), this, ChartElementType.LegendArea, true);
}
#endregion Legend position & size methods
#region Legend Items Fitting Methods
/// <summary>
/// Gets maximum number of rows in all columns.
/// </summary>
/// <returns>Maximum number of rows.</returns>
private int GetMaximumNumberOfRows()
{
return this.GetMaximumNumberOfRows(this._numberOfRowsPerColumn);
}
/// <summary>
/// Gets maximum number of rows in all columns.
/// </summary>
/// <param name="rowsPerColumn">Array that stores number of rows per column.</param>
/// <returns>Maximum number of rows.</returns>
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;
}
/// <summary>
/// Checks if specified legend will fit the specified size.
/// </summary>
/// <param name="graph">Chart graphics.</param>
/// <param name="legendItemsAreaSize">Area that legend items must fit.</param>
/// <param name="numberOfItemsToCheck">Number of items that should be fitted.</param>
/// <param name="fontSizeReducedBy">Number of points the standard legend font is reduced by auto-fitting algorithm.</param>
/// <param name="numberOfColumns">Legend column number.</param>
/// <param name="numberOfRowsPerColumn">Array of number of rows per column.</param>
/// <param name="subColumnSizes">Returns array of sub-column size.</param>
/// <param name="cellHeights">Returns array of cell heights.</param>
/// <param name="horizontalSpaceLeft">Returns horizontal space left.</param>
/// <param name="verticalSpaceLeft">Returns vertical space left.</param>
/// <returns>True if items fit.</returns>
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;
}
/// <summary>
/// Gets maximum number of legend cells defined as Column objects
/// or Cells in the custom legend items.
/// </summary>
/// <returns>Maximum number of cells.</returns>
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
/// <summary>
/// Add all series legend into items collection and then
/// add custom legend items.
/// </summary>
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);
#if !Microsoft_CONTROL
item.MapAreaAttributes = point.ReplaceKeywords(point.LegendMapAreaAttributes);
item.PostBackValue = point.ReplaceKeywords(point.LegendPostBackValue);
item.Url = point.ReplaceKeywords(point.LegendUrl);
#endif
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);
#if !Microsoft_CONTROL
cell.Url = point.ReplaceKeywords(cell.Url);
cell.MapAreaAttributes = point.ReplaceKeywords(cell.MapAreaAttributes);
cell.PostBackValue = point.ReplaceKeywords(cell.PostBackValue);
#endif // !Microsoft_CONTROL
}
}
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 !Microsoft_CONTROL
item.Url = series.ReplaceKeywords(series.LegendUrl);
item.MapAreaAttributes = series.ReplaceKeywords(series.LegendMapAreaAttributes);
item.PostBackValue = series.ReplaceKeywords(series.LegendPostBackValue);
#endif // !Microsoft_CONTROL
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);
#if !Microsoft_CONTROL
cell.Url = series.ReplaceKeywords(cell.Url);
cell.MapAreaAttributes = series.ReplaceKeywords(cell.MapAreaAttributes);
cell.PostBackValue = series.ReplaceKeywords(cell.PostBackValue);
#endif // !Microsoft_CONTROL
}
}
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
/// <summary>
/// Paints legend using chart graphics object.
/// </summary>
/// <param name="chartGraph">The graph provides drawing object to the display device. A Graphics object is associated with a specific device context.</param>
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
/// <summary>
/// Gets or sets the name of the legend.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
SRDescription("DescriptionAttributeLegend_Name"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
]
public override string Name
{
get
{
return base.Name;
}
set
{
base.Name = value;
}
}
/// <summary>
/// Gets or sets the name of the chart area where the legend
/// should be docked.
/// </summary>
[
SRCategory("CategoryAttributeDocking"),
Bindable(true),
DefaultValue(Constants.NotSetValue),
SRDescription("DescriptionAttributeLegend_DockToChartArea"),
TypeConverter(typeof(LegendAreaNameConverter)),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
]
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);
}
}
}
/// <summary>
/// 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.
/// </summary>
[
SRCategory("CategoryAttributeDocking"),
Bindable(true),
DefaultValue(true),
SRDescription("DescriptionAttributeLegend_DockInsideChartArea"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
]
public bool IsDockedInsideChartArea
{
get
{
return _isDockedInsideChartArea;
}
set
{
if(value != _isDockedInsideChartArea)
{
_isDockedInsideChartArea = value;
this.Invalidate(false);
}
}
}
/// <summary>
/// Gets or sets the position of the legend.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
SRDescription("DescriptionAttributeLegend_Position"),
#if Microsoft_CONTROL
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
#else
PersistenceMode(PersistenceMode.InnerProperty),
#endif
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();
#if Microsoft_CONTROL
newPosition.Auto = false;
#else
newPosition.Auto = true;
#endif
newPosition.SetPositionNoAuto(_position.X, _position.Y, _position.Width, _position.Height);
return newPosition;
}
}
return _position;
}
set
{
_position = value;
this.Invalidate(false);
}
}
/// <summary>
/// Determoines if this position should be serialized.
/// </summary>
/// <returns></returns>
internal bool ShouldSerializePosition()
{
return !this.Position.Auto;
}
/// <summary>
/// Gets or sets a property which indicates whether
/// all legend items are equally spaced.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(false),
SRDescription("DescriptionAttributeLegend_EquallySpacedItems"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public bool IsEquallySpacedItems
{
get
{
return _isEquallySpacedItems;
}
set
{
_isEquallySpacedItems = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets a flag which indicates whether the legend is enabled.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(true),
SRDescription("DescriptionAttributeLegend_Enabled"),
NotifyParentPropertyAttribute(true),
ParenthesizePropertyNameAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public bool Enabled
{
get
{
return _enabled;
}
set
{
_enabled = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets a value that indicates if legend text is automatically sized.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(true),
SRDescription("DescriptionAttributeLegend_AutoFitText"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
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);
}
}
/// <summary>
/// Gets or sets the legend style.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(LegendStyle.Table),
SRDescription("DescriptionAttributeLegend_LegendStyle"),
NotifyParentPropertyAttribute(true),
ParenthesizePropertyNameAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public LegendStyle LegendStyle
{
get
{
return _legendStyle;
}
set
{
_legendStyle = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the minimum font size that can be used by the legend text's auto-fitting algorithm.
/// </summary>
[
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);
}
}
/// <summary>
/// Gets or sets the maximum size (in percentage) of the legend used in the automatic layout algorithm.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
[
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);
}
}
/// <summary>
/// Gets a collection of legend columns.
/// </summary>
[
SRCategory("CategoryAttributeCellColumns"),
SRDescription("DescriptionAttributeLegend_CellColumns"),
#if Microsoft_CONTROL
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
#else
PersistenceMode(PersistenceMode.InnerProperty),
#endif
Editor(Editors.LegendCellColumnCollectionEditor.Editor, Editors.LegendCellColumnCollectionEditor.Base),
]
public LegendCellColumnCollection CellColumns
{
get
{
return this._cellColumns;
}
}
/// <summary>
/// Gets the legend table style.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(LegendTableStyle.Auto),
SRDescription("DescriptionAttributeLegend_TableStyle"),
NotifyParentPropertyAttribute(true),
ParenthesizePropertyNameAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public LegendTableStyle TableStyle
{
get
{
return this._legendTableStyle;
}
set
{
this._legendTableStyle = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets the legend header separator style.
/// </summary>
[
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);
}
}
}
/// <summary>
/// Gets or sets the color of the legend header separator.
/// </summary>
[
SRCategory("CategoryAttributeCellColumns"),
DefaultValue(typeof(Color), "Black"),
SRDescription("DescriptionAttributeLegend_HeaderSeparatorColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
public Color HeaderSeparatorColor
{
get
{
return this._headerSeparatorColor;
}
set
{
if(value != this._headerSeparatorColor)
{
this._headerSeparatorColor = value;
this.Invalidate(false);
}
}
}
/// <summary>
/// Gets or sets the separator style of the legend table columns.
/// </summary>
[
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);
}
}
}
/// <summary>
/// Gets or sets the color of the separator of the legend table columns.
/// </summary>
[
SRCategory("CategoryAttributeCellColumns"),
DefaultValue(typeof(Color), "Black"),
SRDescription("DescriptionAttributeLegend_ItemColumnSeparatorColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
public Color ItemColumnSeparatorColor
{
get
{
return this._itemColumnSeparatorColor;
}
set
{
if(value != this._itemColumnSeparatorColor)
{
this._itemColumnSeparatorColor = value;
this.Invalidate(false);
}
}
}
/// <summary>
/// Gets or sets the legend table column spacing, as a percentage of the legend text font.
/// </summary>
[
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);
}
}
}
/// <summary>
/// Gets or sets the legend background color.
/// </summary>
[
DefaultValue(typeof(Color), ""),
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
SRDescription("DescriptionAttributeBackColor"),
NotifyParentPropertyAttribute(true),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color BackColor
{
get
{
return _backColor;
}
set
{
_backColor = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the legend border color.
/// </summary>
[
DefaultValue(typeof(Color), ""),
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
SRDescription("DescriptionAttributeBorderColor"),
NotifyParentPropertyAttribute(true),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color BorderColor
{
get
{
return _borderColor;
}
set
{
_borderColor = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the legend border style.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(ChartDashStyle.Solid),
SRDescription("DescriptionAttributeBorderDashStyle"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public ChartDashStyle BorderDashStyle
{
get
{
return _borderDashStyle;
}
set
{
_borderDashStyle = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the legend border width.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(1),
SRDescription("DescriptionAttributeBorderWidth"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public int BorderWidth
{
get
{
return _borderWidth;
}
set
{
if(value < 0)
{
throw (new ArgumentOutOfRangeException("value", SR.ExceptionLegendBorderWidthIsNegative));
}
_borderWidth = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the legend background image.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(""),
SRDescription("DescriptionAttributeBackImage"),
Editor(Editors.ImageValueEditor.Editor, Editors.ImageValueEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
NotifyParentPropertyAttribute(true),
]
public string BackImage
{
get
{
return _backImage;
}
set
{
_backImage = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the legend background image drawing mode.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(ChartImageWrapMode.Tile),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeImageWrapMode"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public ChartImageWrapMode BackImageWrapMode
{
get
{
return _backImageWrapMode;
}
set
{
_backImageWrapMode = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets a color which will be replaced with a transparent color while drawing the background image.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), ""),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeImageTransparentColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color BackImageTransparentColor
{
get
{
return _backImageTransparentColor;
}
set
{
_backImageTransparentColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the background image alignment used for the unscaled drawing mode.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(ChartImageAlignmentStyle.TopLeft),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeBackImageAlign"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public ChartImageAlignmentStyle BackImageAlignment
{
get
{
return _backImageAlignment;
}
set
{
_backImageAlignment = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets background gradient style of the legend.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(GradientStyle.None),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeBackGradientStyle"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
Editor(Editors.GradientEditor.Editor, Editors.GradientEditor.Base)
]
public GradientStyle BackGradientStyle
{
get
{
return _backGradientStyle;
}
set
{
_backGradientStyle = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the secondary background color.
/// <seealso cref="BackColor"/>
/// <seealso cref="BackHatchStyle"/>
/// <seealso cref="BackGradientStyle"/>
/// </summary>
/// <value>
/// A <see cref="Color"/> value used for the secondary color of background with
/// hatching or gradient fill.
/// </value>
/// <remarks>
/// This color is used with <see cref="BackColor"/> when <see cref="BackHatchStyle"/> or
/// <see cref="BackGradientStyle"/> are used.
/// </remarks>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), ""),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeBackSecondaryColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color BackSecondaryColor
{
get
{
return _backSecondaryColor;
}
set
{
_backSecondaryColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the background hatch style.
/// <seealso cref="BackSecondaryColor"/>
/// <seealso cref="BackColor"/>
/// <seealso cref="BackGradientStyle"/>
/// </summary>
/// <value>
/// A <see cref="ChartHatchStyle"/> value used for the background.
/// </value>
/// <remarks>
/// Two colors are used to draw the hatching, <see cref="BackColor"/> and <see cref="BackSecondaryColor"/>.
/// </remarks>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(ChartHatchStyle.None),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeBackHatchStyle"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
Editor(Editors.HatchStyleEditor.Editor, Editors.HatchStyleEditor.Base)
]
public ChartHatchStyle BackHatchStyle
{
get
{
return _backHatchStyle;
}
set
{
_backHatchStyle = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the font of the legend text.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Font), "Microsoft Sans Serif, 8pt"),
SRDescription("DescriptionAttributeLegend_Font"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Font Font
{
get
{
return _font;
}
set
{
this.IsTextAutoFit = false;
_font = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the color of the legend text.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), "Black"),
SRDescription("DescriptionAttributeLegendFontColor"),
NotifyParentPropertyAttribute(true),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color ForeColor
{
get
{
return _foreColor;
}
set
{
_foreColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the text alignment.
/// </summary>
[
SRCategory("CategoryAttributeDocking"),
Bindable(true),
DefaultValue(StringAlignment.Near),
SRDescription("DescriptionAttributeLegend_Alignment"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public StringAlignment Alignment
{
get
{
return _legendAlignment;
}
set
{
_legendAlignment = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the property that specifies where the legend docks.
/// </summary>
[
SRCategory("CategoryAttributeDocking"),
Bindable(true),
DefaultValue(Docking.Right),
SRDescription("DescriptionAttributeLegend_Docking"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Docking Docking
{
get
{
return _legendDocking;
}
set
{
_legendDocking = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the offset between the legend and its shadow.
/// <seealso cref="ShadowColor"/>
/// </summary>
/// <value>
/// An integer value that represents the offset between the legend and its shadow.
/// </value>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(0),
SRDescription("DescriptionAttributeShadowOffset"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public int ShadowOffset
{
get
{
return _shadowOffset;
}
set
{
_shadowOffset = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the color of a legend's shadow.
/// <seealso cref="ShadowOffset"/>
/// </summary>
/// <value>
/// A <see cref="Color"/> value used to draw a legend's shadow.
/// </value>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), "128, 0, 0, 0"),
SRDescription("DescriptionAttributeShadowColor"),
NotifyParentPropertyAttribute(true),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color ShadowColor
{
get
{
return _shadowColor;
}
set
{
_shadowColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the name of the chart area name inside which the legend is drawn.
/// </summary>
[
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);
}
}
/// <summary>
/// Gets the custom legend items.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeLegend_CustomItems"),
#if Microsoft_CONTROL
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
#else
PersistenceMode(PersistenceMode.InnerProperty),
#endif
Editor(Editors.LegendItemCollectionEditor.Editor, Editors.LegendItemCollectionEditor.Base),
]
public LegendItemsCollection CustomItems
{
get
{
return _customLegends;
}
}
/// <summary>
/// Gets or sets a property that defines the preferred number of characters in a line of the legend text.
/// </summary>
/// <remarks>
/// When legend text exceeds the value defined in the <b>TextWrapThreshold</b> 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.
/// </remarks>
[
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);
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// When the <b>LegendItemOrder</b> property is set to <b>Auto</b>, the legend will automatically be reversed
/// if StackedColumn, StackedColumn100, StackedArea or StackedArea100 chart types are used.
/// </remarks>
[
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);
}
}
}
/// <summary>
/// Gets or sets a flag which indicates whether
/// legend rows should be drawn with interlaced background color.
/// </summary>
[
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);
}
}
}
/// <summary>
/// Gets or sets the legend interlaced row's background color. Only applicable if interlaced rows are used.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
DefaultValue(typeof(Color), ""),
SRDescription("DescriptionAttributeLegend_InterlacedRowsColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
public Color InterlacedRowsColor
{
get
{
return this._interlacedRowsColor;
}
set
{
if(value != this._interlacedRowsColor)
{
this._interlacedRowsColor = value;
this.Invalidate(false);
}
}
}
#endregion
#region Legend Title Properties
/// <summary>
/// Gets or sets the title text of the legend.
/// </summary>
[
SRCategory("CategoryAttributeTitle"),
DefaultValue(""),
SRDescription("DescriptionAttributeLegend_Title"),
]
public string Title
{
get
{
return this._title;
}
set
{
if(value != this._title)
{
this._title = value;
this.Invalidate(false);
}
}
}
/// <summary>
/// Gets or sets the text color of the legend title.
/// </summary>
[
SRCategory("CategoryAttributeTitle"),
DefaultValue(typeof(Color), "Black"),
SRDescription("DescriptionAttributeLegend_TitleColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
public Color TitleForeColor
{
get
{
return this._titleForeColor;
}
set
{
if(value != this._titleForeColor)
{
this._titleForeColor = value;
this.Invalidate(false);
}
}
}
/// <summary>
/// Gets or sets the background color of the legend title.
/// </summary>
[
SRCategory("CategoryAttributeTitle"),
DefaultValue(typeof(Color), ""),
SRDescription("DescriptionAttributeTitleBackColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
public Color TitleBackColor
{
get
{
return this._titleBackColor;
}
set
{
if(value != this._titleBackColor)
{
this._titleBackColor = value;
this.Invalidate(false);
}
}
}
/// <summary>
/// Gets or sets the font of the legend title.
/// </summary>
[
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);
}
}
}
/// <summary>
/// Gets or sets the text alignment of the legend title.
/// </summary>
[
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);
}
}
}
/// <summary>
/// Gets or sets the separator style of the legend title.
/// </summary>
[
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);
}
}
}
/// <summary>
/// Gets or sets the separator color of the legend title.
/// </summary>
[
SRCategory("CategoryAttributeTitle"),
DefaultValue(typeof(Color), "Black"),
SRDescription("DescriptionAttributeLegend_TitleSeparatorColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
public Color TitleSeparatorColor
{
get
{
return this._titleSeparatorColor;
}
set
{
if(value != this._titleSeparatorColor)
{
this._titleSeparatorColor = value;
this.Invalidate(false);
}
}
}
#endregion // Legend Title Properties
#region Legent Title and Header Helper methods
/// <summary>
/// Gets legend title size in relative coordinates.
/// </summary>
/// <param name="chartGraph">Chart graphics.</param>
/// <param name="titleMaxSize">Maximum possible legend title size.</param>
/// <returns>Legend yitle size.</returns>
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;
}
/// <summary>
/// Gets legend header size in relative coordinates.
/// </summary>
/// <param name="chartGraph">Chart graphics.</param>
/// <param name="legendColumn">Legend column to get the header for.</param>
/// <returns>Legend yitle size.</returns>
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;
}
/// <summary>
/// Draw Legend header.
/// </summary>
/// <param name="chartGraph">Chart graphics to draw the header on.</param>
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 );
}
}
}
/// <summary>
/// Draw Legend title.
/// </summary>
/// <param name="chartGraph">Chart graphics to draw the title on.</param>
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);
}
}
}
/// <summary>
/// Gets legend separator size in pixels
/// </summary>
/// <param name="separatorType">Separator type.</param>
/// <returns>Separator size in relative coordinates.</returns>
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;
}
/// <summary>
/// Draws specified legend separator.
/// </summary>
/// <param name="chartGraph">Chart graphics.</param>
/// <param name="separatorType">Separator type.</param>
/// <param name="color">Separator color.</param>
/// <param name="horizontal">Flag that determines if separator is vertical or horizontal.</param>
/// <param name="position">Separator position.</param>
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
/// <summary>
/// Get visible legend border size.
/// </summary>
/// <returns>Visible legend border size.</returns>
private int GetBorderSize()
{
if(this.BorderWidth > 0 &&
this.BorderDashStyle != ChartDashStyle.NotSet &&
!this.BorderColor.IsEmpty &&
this.BorderColor != Color.Transparent)
{
return this.BorderWidth;
}
return 0;
}
/// <summary>
/// Helper method which returns current legend table style.
/// </summary>
/// <param name="chartGraph">Chart graphics.</param>
/// <returns>Legend table style.</returns>
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;
}
/// <summary>
/// Helper method that checks if legend is enabled.
/// </summary>
/// <returns>True if legend is enabled.</returns>
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;
}
/// <summary>
/// Invalidate chart legend when one of the properties is changed
/// </summary>
/// <param name="invalidateLegendOnly">Indicates that only legend area should be invalidated.</param>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "This parameter is used when compiling for the Microsoft version of Chart")]
internal void Invalidate(bool invalidateLegendOnly)
{
#if Microsoft_CONTROL
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();
}
}
#endif
}
#endregion
#region IDisposable Members
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
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
}
/// <summary>
/// The LegendCollection class is a strongly typed collection of legends.
/// </summary>
[
SRDescription("DescriptionAttributeLegendCollection_LegendCollection"),
]
#if ASPPERM_35
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
public class LegendCollection : ChartNamedElementCollection<Legend>
{
#region Constructors
/// <summary>
/// LegendCollection constructor.
/// </summary>
/// <param name="chartPicture">Chart picture object.</param>
internal LegendCollection(ChartPicture chartPicture)
: base(chartPicture)
{
}
#endregion
#region Properties
/// <summary>
/// Gets the default legend name.
/// </summary>
internal string DefaultNameReference
{
get { return this.Count > 0 ? this[0].Name : String.Empty; }
}
#endregion
#region Methods
/// <summary>
/// Creates a new Legend with the specified name and adds it to the collection.
/// </summary>
/// <param name="name">The new chart area name.</param>
/// <returns>New legend</returns>
public Legend Add(string name)
{
Legend legend = new Legend(name);
this.Add(legend);
return legend;
}
/// <summary>
/// Recalculates legend position in the collection.
/// </summary>
/// <param name="chartGraph">Chart graphics used.</param>
/// <param name="chartAreasRectangle">Area where the legend should be positioned.</param>
/// <param name="elementSpacing">Spacing size as a percentage of the area.</param>
internal void CalcLegendPosition(
ChartGraphics chartGraph,
ref RectangleF chartAreasRectangle,
float elementSpacing)
{
// Loop through all legends
foreach(Legend legend in this)
{
// Calculate position of the legends docked to the chart picture
if(legend.IsEnabled() &&
legend.DockedToChartArea == Constants.NotSetValue &&
legend.Position.Auto)
{
legend.CalcLegendPosition(chartGraph, ref chartAreasRectangle, elementSpacing);
}
}
}
/// <summary>
/// Recalculates legend position in the collection for legends docked outside of chart area.
/// </summary>
/// <param name="chartGraph">Chart graphics used.</param>
/// <param name="area">Area the legend is docked to.</param>
/// <param name="chartAreasRectangle">Area where the legend should be positioned.</param>
/// <param name="elementSpacing">Spacing size as a percentage of the area.</param>
internal void CalcOutsideLegendPosition(
ChartGraphics chartGraph,
ChartArea area,
ref RectangleF chartAreasRectangle,
float elementSpacing)
{
if(Common != null && Common.ChartPicture != null)
{
// Get elemets spacing
float areaSpacing = Math.Min((chartAreasRectangle.Height/100F) * elementSpacing, (chartAreasRectangle.Width/100F) * elementSpacing);
// Loop through all legends
foreach(Legend legend in this)
{
// Check if all chart area names are valid
if (legend.DockedToChartArea != Constants.NotSetValue && this.Chart.ChartAreas.IndexOf(legend.DockedToChartArea)<0)
{
throw (new ArgumentException(SR.ExceptionLegendDockedChartAreaIsMissing((string)legend.DockedToChartArea)));
}
// Process only legends docked to specified area
if(legend.IsEnabled() &&
legend.IsDockedInsideChartArea == false &&
legend.DockedToChartArea == area.Name &&
legend.Position.Auto)
{
// Calculate legend position
legend.CalcLegendPosition(chartGraph,
ref chartAreasRectangle,
areaSpacing);
// Adjust legend position
RectangleF legendPosition = legend.Position.ToRectangleF();
if(legend.Docking == Docking.Top)
{
legendPosition.Y -= areaSpacing;
if(!area.Position.Auto)
{
legendPosition.Y -= legendPosition.Height;
}
}
else if(legend.Docking == Docking.Bottom)
{
legendPosition.Y += areaSpacing;
if(!area.Position.Auto)
{
legendPosition.Y = area.Position.Bottom + areaSpacing;
}
}
if(legend.Docking == Docking.Left)
{
legendPosition.X -= areaSpacing;
if(!area.Position.Auto)
{
legendPosition.X -= legendPosition.Width;
}
}
if(legend.Docking == Docking.Right)
{
legendPosition.X += areaSpacing;
if(!area.Position.Auto)
{
legendPosition.X = area.Position.Right + areaSpacing;
}
}
legend.Position.SetPositionNoAuto(legendPosition.X, legendPosition.Y, legendPosition.Width, legendPosition.Height);
}
}
}
}
/// <summary>
/// Recalculates legend position inside chart area in the collection.
/// </summary>
/// <param name="chartGraph">Chart graphics used.</param>
/// <param name="elementSpacing">Spacing size as a percentage of the area.</param>
internal void CalcInsideLegendPosition(
ChartGraphics chartGraph,
float elementSpacing)
{
if(Common != null && Common.ChartPicture != null)
{
// Check if all chart area names are valid
foreach(Legend legend in this)
{
if (legend.DockedToChartArea != Constants.NotSetValue)
{
try
{
ChartArea area = Common.ChartPicture.ChartAreas[legend.DockedToChartArea];
}
catch
{
throw(new ArgumentException( SR.ExceptionLegendDockedChartAreaIsMissing( (string)legend.DockedToChartArea ) ) );
}
}
}
// Loop through all chart areas
foreach (ChartArea area in Common.ChartPicture.ChartAreas)
{
// Check if chart area is visible
if(area.Visible)
{
// Get area position
RectangleF legendPlottingRectangle = area.PlotAreaPosition.ToRectangleF();
// Get elemets spacing
float areaSpacing = Math.Min((legendPlottingRectangle.Height/100F) * elementSpacing, (legendPlottingRectangle.Width/100F) * elementSpacing);
// Loop through all legends
foreach(Legend legend in this)
{
if(legend.IsEnabled() &&
legend.IsDockedInsideChartArea == true &&
legend.DockedToChartArea == area.Name &&
legend.Position.Auto)
{
// Calculate legend position
legend.CalcLegendPosition(chartGraph,
ref legendPlottingRectangle,
areaSpacing);
}
}
}
}
}
}
#endregion
#region Event handlers
internal void ChartAreaNameReferenceChanged(object sender, NameReferenceChangedEventArgs e)
{
//If all the chart areas are removed and then the first one is added we don't want to dock the legends
if (e.OldElement == null)
return;
foreach (Legend legend in this)
if (legend.DockedToChartArea == e.OldName)
legend.DockedToChartArea = e.NewName;
}
#endregion
}
/// <summary>
/// The LegendItemsCollection class is a strongly typed collection of legend items.
/// </summary>
[
SRDescription("DescriptionAttributeCustomLabelsCollection_CustomLabelsCollection"),
]
#if ASPPERM_35
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
public class LegendItemsCollection : ChartElementCollection<LegendItem>
{
#region Constructors
/// <summary>
/// LegendItemsCollection constructor
/// </summary>
internal LegendItemsCollection(Legend legend)
: base(legend)
{
}
#endregion
#region Methods
/// <summary>
/// Adds a legend item into the collection.
/// </summary>
/// <param name="color">Legend item color.</param>
/// <param name="text">Legend item text.</param>
/// <returns>Index of newly added item.</returns>
public int Add(Color color, string text)
{
LegendItem item = new LegendItem(text, color, "");
Add(item);
return Count - 1;
}
/// <summary>
/// Insert a legend item into the collection.
/// </summary>
/// <param name="index">Index to insert at.</param>
/// <param name="color">Legend item color.</param>
/// <param name="text">Legend item text.</param>
/// <returns>Index of newly added item.</returns>
public void Insert(int index, Color color, string text)
{
LegendItem item = new LegendItem(text, color, "");
this.Insert(index, item);
}
/// <summary>
/// Adds a legend item into the collection.
/// </summary>
/// <param name="image">Legend item image.</param>
/// <param name="text">Legend item text.</param>
/// <returns>Index of newly added item.</returns>
public int Add(string image, string text)
{
LegendItem item = new LegendItem(text, Color.Empty, image);
Add(item);
return Count-1;
}
/// <summary>
/// Insert one legend item into the collection.
/// </summary>
/// <param name="index">Index to insert at.</param>
/// <param name="image">Legend item image.</param>
/// <param name="text">Legend item text.</param>
/// <returns>Index of newly added item.</returns>
public void Insert(int index, string image, string text)
{
LegendItem item = new LegendItem(text, Color.Empty, image);
this.Insert(index, item);
}
/// <summary>
/// Reverses the order of items in the collection.
/// </summary>
public void Reverse()
{
List<LegendItem> list = this.Items as List<LegendItem>;
list.Reverse();
Invalidate();
}
#endregion
}
/// <summary>
/// The LegendItem class represents a single item (row) in the legend.
/// It contains properties which describe visual appearance and
/// content of the legend item.
/// </summary>
[
SRDescription("DescriptionAttributeLegendItem_LegendItem"),
DefaultProperty("Name"),
]
#if Microsoft_CONTROL
public class LegendItem : ChartNamedElement
#else
#if ASPPERM_35
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
public class LegendItem : ChartNamedElement, IChartMapArea
#endif
{
#region Fields
// Private data members, which store properties values
private Color _color = Color.Empty;
private string _image = "";
private string _seriesName = "";
private int _seriesPointIndex = -1;
// Chart image map properties
private string _toolTip = "";
#if !Microsoft_CONTROL
private string _url = "";
private string _attributes = "";
private string _postbackValue = String.Empty;
#endif
// Additional appearance properties
internal LegendImageStyle style = LegendImageStyle.Rectangle;
internal GradientStyle backGradientStyle = GradientStyle.None;
internal Color backSecondaryColor = Color.Empty;
internal Color backImageTransparentColor = Color.Empty;
internal Color borderColor = Color.Black;
internal int borderWidth = 1;
internal ChartDashStyle borderDashStyle = ChartDashStyle.Solid;
internal ChartHatchStyle backHatchStyle = ChartHatchStyle.None;
internal int shadowOffset = 0;
internal Color shadowColor = Color.FromArgb(128, 0, 0, 0);
internal ChartImageWrapMode backImageWrapMode = ChartImageWrapMode.Tile;
internal ChartImageAlignmentStyle backImageAlign = ChartImageAlignmentStyle.TopLeft;
// Marker properties
internal MarkerStyle markerStyle = MarkerStyle.None;
internal int markerSize = 5;
internal string markerImage = "";
internal Color markerImageTransparentColor = Color.Empty;
internal Color markerColor = Color.Empty;
internal Color markerBorderColor = Color.Empty;
// True if legend item is enabled.
private bool _enabled = true;
// Series marker border width
private int _markerBorderWidth = 1;
// Collection of legend item cells
private LegendCellCollection _cells = null;
// Legend item visual separator
private LegendSeparatorStyle _separatorType = LegendSeparatorStyle.None;
// Legend item visual separator color
private Color _separatorColor = Color.Black;
// Indicates that temporary cells where added and thet have to be removed
internal bool clearTempCells = false;
#endregion
#region Constructors
/// <summary>
/// LegendItem constructor
/// </summary>
public LegendItem()
{
// Create collection of legend item cells
this._cells = new LegendCellCollection(this);
#if !Microsoft_CONTROL
this.PostBackValue = String.Empty;
#endif //!WIN_CONTROL
}
/// <summary>
/// LegendItem constructor
/// </summary>
/// <param name="name">Item name.</param>
/// <param name="color">Item color.</param>
/// <param name="image">Item image.</param>
public LegendItem(string name, Color color, string image) : base (name)
{
this._color = color;
this._image = image;
// Create collection of legend item cells
this._cells = new LegendCellCollection(this);
#if !Microsoft_CONTROL
this.PostBackValue = String.Empty;
#endif //!WIN_CONTROL
}
#endregion
#region Legend item properties
/// <summary>
/// Gets the Legend object which the item belongs to.
/// </summary>
[
Bindable(false),
Browsable(false),
DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
SerializationVisibilityAttribute(SerializationVisibility.Hidden),
]
public Legend Legend
{
get
{
if (Parent != null)
return Parent.Parent as Legend;
else
return null;
}
}
/// <summary>
/// Gets or sets the name of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
SRDescription("DescriptionAttributeLegendItem_Name"),
NotifyParentPropertyAttribute(true),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
ParenthesizePropertyNameAttribute(true)
]
public override string Name
{
get
{
return base.Name;
}
set
{
base.Name = value;
}
}
/// <summary>
/// Gets or sets the color of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
SRDescription("DescriptionAttributeLegendItem_Color"),
DefaultValue(typeof(Color), ""),
NotifyParentPropertyAttribute(true),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color Color
{
get
{
return _color;
}
set
{
_color = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets a string value that represents a URL to an image file, which will be used for the legend item's symbol.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
SRDescription("DescriptionAttributeLegendItem_Image"),
DefaultValue(""),
Editor(Editors.ImageValueEditor.Editor, Editors.ImageValueEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
NotifyParentPropertyAttribute(true)
]
public string Image
{
get
{
return _image;
}
set
{
_image = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the picture style of the legend item image.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(LegendImageStyle), "Rectangle"),
SRDescription("DescriptionAttributeLegendItem_Style"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
ParenthesizePropertyNameAttribute(true)
]
public LegendImageStyle ImageStyle
{
get
{
return style;
}
set
{
style = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the border color of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), "Black"),
SRDescription("DescriptionAttributeBorderColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color BorderColor
{
get
{
return borderColor;
}
set
{
borderColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the background hatch style of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(ChartHatchStyle.None),
SRDescription("DescriptionAttributeBackHatchStyle"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
Editor(Editors.HatchStyleEditor.Editor, Editors.HatchStyleEditor.Base)
]
public ChartHatchStyle BackHatchStyle
{
get
{
return backHatchStyle;
}
set
{
backHatchStyle = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets a color which will be replaced with a transparent color while drawing the background image.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), ""),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeImageTransparentColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color BackImageTransparentColor
{
get
{
return backImageTransparentColor;
}
set
{
backImageTransparentColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets background gradient style of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(GradientStyle.None),
SRDescription("DescriptionAttributeBackGradientStyle"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
Editor(Editors.GradientEditor.Editor, Editors.GradientEditor.Base)
]
public GradientStyle BackGradientStyle
{
get
{
return backGradientStyle;
}
set
{
backGradientStyle = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the secondary background color.
/// <seealso cref="Color"/>
/// <seealso cref="BackHatchStyle"/>
/// <seealso cref="BackGradientStyle"/>
/// </summary>
/// <value>
/// A <see cref="Color"/> value used for the secondary color of background with
/// hatching or gradient fill.
/// </value>
/// <remarks>
/// This color is used with <see cref="Color"/> when <see cref="BackHatchStyle"/> or
/// <see cref="BackGradientStyle"/> are used.
/// </remarks>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), ""),
SRDescription("DescriptionAttributeBackSecondaryColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color BackSecondaryColor
{
get
{
return backSecondaryColor;
}
set
{
if(value != Color.Empty && (value.A != 255 || value == Color.Transparent))
{
throw (new ArgumentException(SR.ExceptionBackSecondaryColorIsTransparent));
}
backSecondaryColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the border width of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(1),
SRDescription("DescriptionAttributeBorderWidth"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public int BorderWidth
{
get
{
return borderWidth;
}
set
{
if(value < 0)
{
throw (new ArgumentOutOfRangeException("value", SR.ExceptionBorderWidthIsZero));
}
borderWidth = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets a flag which indicates whether the Legend item is enabled.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
DefaultValue(true),
SRDescription("DescriptionAttributeLegendItem_Enabled"),
ParenthesizePropertyNameAttribute(true),
]
public bool Enabled
{
get
{
return this._enabled;
}
set
{
this._enabled = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the marker border width of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeMarker"),
DefaultValue(1),
SRDescription("DescriptionAttributeMarkerBorderWidth"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public int MarkerBorderWidth
{
get
{
return this._markerBorderWidth;
}
set
{
if(value < 0)
{
throw (new ArgumentOutOfRangeException("value", SR.ExceptionLegendMarkerBorderWidthIsNegative));
}
this._markerBorderWidth = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the legend item border style.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(ChartDashStyle.Solid),
SRDescription("DescriptionAttributeBorderDashStyle"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public ChartDashStyle BorderDashStyle
{
get
{
return borderDashStyle;
}
set
{
borderDashStyle = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the offset between the legend item and its shadow.
/// <seealso cref="ShadowColor"/>
/// </summary>
/// <value>
/// An integer value that represents the offset between the legend item and its shadow.
/// </value>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
SRDescription("DescriptionAttributeShadowOffset"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.InnerProperty),
#endif
DefaultValue(0)
]
public int ShadowOffset
{
get
{
return shadowOffset;
}
set
{
shadowOffset = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the color of a legend item's shadow.
/// <seealso cref="ShadowOffset"/>
/// </summary>
/// <value>
/// A <see cref="Color"/> value used to draw a legend item's shadow.
/// </value>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), "128,0,0,0"),
SRDescription("DescriptionAttributeShadowColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public Color ShadowColor
{
get
{
return shadowColor;
}
set
{
shadowColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the marker style of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeMarker"),
Bindable(true),
DefaultValue(MarkerStyle.None),
SRDescription("DescriptionAttributeLegendItem_MarkerStyle"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
Editor(Editors.MarkerStyleEditor.Editor, Editors.MarkerStyleEditor.Base),
RefreshProperties(RefreshProperties.All)
]
public MarkerStyle MarkerStyle
{
get
{
return markerStyle;
}
set
{
markerStyle = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the marker size of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeMarker"),
Bindable(true),
DefaultValue(5),
SRDescription("DescriptionAttributeLegendItem_MarkerSize"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
RefreshProperties(RefreshProperties.All)
]
public int MarkerSize
{
get
{
return markerSize;
}
set
{
markerSize = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the marker image of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeMarker"),
Bindable(true),
DefaultValue(""),
SRDescription("DescriptionAttributeMarkerImage"),
Editor(Editors.ImageValueEditor.Editor, Editors.ImageValueEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
RefreshProperties(RefreshProperties.All)
]
public string MarkerImage
{
get
{
return markerImage;
}
set
{
markerImage = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets a color which will be replaced with a transparent color while drawing the marker image.
/// </summary>
[
SRCategory("CategoryAttributeMarker"),
Bindable(true),
DefaultValue(typeof(Color), ""),
SRDescription("DescriptionAttributeImageTransparentColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
RefreshProperties(RefreshProperties.All)
]
public Color MarkerImageTransparentColor
{
get
{
return markerImageTransparentColor;
}
set
{
markerImageTransparentColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the marker color of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeMarker"),
Bindable(true),
DefaultValue(typeof(Color), ""),
SRDescription("DescriptionAttributeLegendItem_MarkerColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
RefreshProperties(RefreshProperties.All)
]
public Color MarkerColor
{
get
{
return markerColor;
}
set
{
markerColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the marker border color of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeMarker"),
Bindable(true),
DefaultValue(typeof(Color), ""),
SRDescription("DescriptionAttributeMarkerBorderColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
#endif
RefreshProperties(RefreshProperties.All)
]
public Color MarkerBorderColor
{
get
{
return markerBorderColor;
}
set
{
markerBorderColor = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the series name of the legend item..
/// </summary>
[
Browsable(false),
DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
SerializationVisibilityAttribute(SerializationVisibility.Hidden),
SRDescription("DescriptionAttributeLegendItem_SeriesName"),
DefaultValue("")
]
public string SeriesName
{
get
{
return _seriesName;
}
set
{
_seriesName = value;
}
}
/// <summary>
/// Gets or sets the index of the legend item's associated DataPoint object.
/// </summary>
[
Browsable(false),
DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
SerializationVisibilityAttribute(SerializationVisibility.Hidden),
SRDescription("DescriptionAttributeLegendItem_SeriesPointIndex"),
DefaultValue(-1)
]
public int SeriesPointIndex
{
get
{
return _seriesPointIndex;
}
set
{
_seriesPointIndex = value;
}
}
/// <summary>
/// Gets or sets the separator style of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
DefaultValue(typeof(LegendSeparatorStyle), "None"),
SRDescription("DescriptionAttributeLegendItem_Separator"),
]
public LegendSeparatorStyle SeparatorType
{
get
{
return this._separatorType;
}
set
{
if(value != this._separatorType)
{
this._separatorType = value;
this.Invalidate(false);
}
}
}
/// <summary>
/// Gets or sets the separator color of the legend item.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
DefaultValue(typeof(Color), "Black"),
SRDescription("DescriptionAttributeLegendItem_SeparatorColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
public Color SeparatorColor
{
get
{
return this._separatorColor;
}
set
{
if(value != this._separatorColor)
{
this._separatorColor = value;
this.Invalidate(false);
}
}
}
/// <summary>
/// The LegendCellCollection class is a collection of legend item cells.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
SRDescription("DescriptionAttributeLegendItem_Cells"),
#if Microsoft_CONTROL
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
#else
PersistenceMode(PersistenceMode.InnerProperty),
#endif
Editor(Editors.LegendCellCollectionEditor.Editor, Editors.LegendCellCollectionEditor.Base),
]
public LegendCellCollection Cells
{
get
{
return this._cells;
}
}
#endregion
#region IMapAreaAttributesutes Properties implementation
/// <summary>
/// Tooltip of the area.
/// </summary>
[
SRCategory("CategoryAttributeMapArea"),
Bindable(true),
SRDescription("DescriptionAttributeToolTip"),
DefaultValue(""),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
public string ToolTip
{
set
{
_toolTip = value;
#if Microsoft_CONTROL
if(Chart != null && Chart.selection != null)
{
Chart.selection.enabledChecked = false;
}
#endif
}
get
{
return _toolTip;
}
}
#if !Microsoft_CONTROL
/// <summary>
/// URL target of the area.
/// </summary>
[
SRCategory("CategoryAttributeMapArea"),
Bindable(true),
SRDescription("DescriptionAttributeUrl"),
DefaultValue(""),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute),
Editor(Editors.UrlValueEditor.Editor, Editors.UrlValueEditor.Base)
#endif
]
public string Url
{
set
{
_url = value;
}
get
{
return _url;
}
}
#endif
#if !Microsoft_CONTROL
/// <summary>
/// Other attributes of the area.
/// </summary>
[
SRCategory("CategoryAttributeMapArea"),
Bindable(true),
SRDescription("DescriptionAttributeMapAreaAttributes"),
DefaultValue(""),
PersistenceMode(PersistenceMode.Attribute)
]
public string MapAreaAttributes
{
set
{
_attributes = value;
}
get
{
return _attributes;
}
}
/// <summary>
/// Gets or sets the postback value which can be processed on a click event.
/// </summary>
/// <value>The value which is passed to a click event as an argument.</value>
[DefaultValue("")]
[SRCategory(SR.Keys.CategoryAttributeMapArea)]
[SRDescription(SR.Keys.DescriptionAttributePostBackValue)]
public string PostBackValue
{
get
{
return this._postbackValue;
}
set
{
this._postbackValue = value;
}
}
#endif //!Microsoft_CONTROL
#endregion
#region Helper methods
/// <summary>
/// Helper method adds default legend item cells based on the columns
/// specified. If columns collection is empty we assume the presence of
/// two columns: series marker and legend item text.
/// </summary>
/// <param name="legend">Legend this item belongs to.</param>
internal void AddAutomaticCells(Legend legend)
{
// Check if cells defined
if(this.Cells.Count == 0)
{
// Check if legend item was generated for the series
if(this.SeriesName.Length > 0)
{
// If legend do not have any columns set add a series marker
// and legend text cells
if(legend.CellColumns.Count == 0)
{
// VSTS 96787 - Text Direction (RTL/LTR)
if (legend.Common != null && legend.Common.ChartPicture.RightToLeft == RightToLeft.Yes)
{
this.Cells.Add(LegendCellType.Text, KeywordName.LegendText, ContentAlignment.MiddleLeft);
this.Cells.Add(LegendCellType.SeriesSymbol, string.Empty, ContentAlignment.MiddleCenter);
}
else
{
this.Cells.Add(LegendCellType.SeriesSymbol, string.Empty, ContentAlignment.MiddleCenter);
this.Cells.Add(LegendCellType.Text, KeywordName.LegendText, ContentAlignment.MiddleLeft);
}
}
else
{
// Add cell for each of the columns
foreach(LegendCellColumn legendColumn in legend.CellColumns)
{
this.Cells.Add(legendColumn.CreateNewCell());
}
}
}
else
{
// Add Marker plus text for everything else
this.clearTempCells = true;
this.Cells.Add(LegendCellType.SeriesSymbol, string.Empty, ContentAlignment.MiddleCenter);
this.Cells.Add(LegendCellType.Text, KeywordName.LegendText, ContentAlignment.MiddleLeft);
}
}
}
/// <summary>
/// Sets legend item properties from the series
/// </summary>
/// <param name="series">Series object.</param>
/// <param name="common">Common elements object.</param>
internal void SetAttributes(CommonElements common, Series series)
{
// Get legend item picture style
IChartType chartType = common.ChartTypeRegistry.GetChartType(series.ChartTypeName);
style = chartType.GetLegendImageStyle(series);
// Set series name
_seriesName = series.Name;
// Get shadow properties
shadowOffset = series.ShadowOffset;
shadowColor = series.ShadowColor;
// Check if series is drawn in 3D chart area
bool area3D = common.Chart.ChartAreas[series.ChartArea].Area3DStyle.Enable3D;
// Get other properties
SetAttributes((DataPointCustomProperties) series, area3D);
}
/// <summary>
/// Sets legend item properties from the DataPointCustomProperties object.
/// </summary>
/// <param name="properties">DataPointCustomProperties object.</param>
/// <param name="area3D">Element belongs to the 3D area.</param>
internal void SetAttributes(DataPointCustomProperties properties, bool area3D)
{
borderColor = properties.BorderColor;
borderWidth = properties.BorderWidth;
borderDashStyle = properties.BorderDashStyle;
markerStyle = properties.MarkerStyle;
markerSize = properties.MarkerSize;
markerImage = properties.MarkerImage;
markerImageTransparentColor = properties.MarkerImageTransparentColor;
markerColor = properties.MarkerColor;
markerBorderColor = properties.MarkerBorderColor;
this._markerBorderWidth = properties.MarkerBorderWidth;
float dpi = 96;
if(Common != null)
dpi = Common.graph.Graphics.DpiX;
int maxBorderWidth = (int)Math.Round((2 * dpi) / 96);
if (this._markerBorderWidth > maxBorderWidth)
{
this._markerBorderWidth = maxBorderWidth;
}
if(properties.MarkerBorderWidth <= 0)
{
markerBorderColor = Color.Transparent;
}
// Improve readability of the line series marker by using at least 2 pixel wide lines
if(this.style == LegendImageStyle.Line &&
borderWidth <= (int)Math.Round(dpi / 96) )
{
borderWidth = maxBorderWidth;
}
if(!area3D)
{
backGradientStyle = properties.BackGradientStyle;
backSecondaryColor = properties.BackSecondaryColor;
backImageTransparentColor = properties.BackImageTransparentColor;
backImageWrapMode = properties.BackImageWrapMode;
backImageAlign = properties.BackImageAlignment;
backHatchStyle = properties.BackHatchStyle;
}
}
/// <summary>
/// Invalidate chart (or just legend )when collection is changed
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "This parameter is used when compiling for the Microsoft version of Chart")]
private void Invalidate(bool invalidateLegendOnly)
{
#if Microsoft_CONTROL
if(Legend != null)
{
// Invalidate control
Legend.Invalidate(invalidateLegendOnly);
}
#endif
}
#endregion
#region IDisposable Members
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_cells != null)
{
_cells.Dispose();
_cells = null;
}
}
base.Dispose(disposing);
}
#endregion
}
}
|