Files
Pat Tullmann 0cb742dafb binfmt-detector-cli: rewrite to support PE32+ binaries (#38)
Rewrite with hard-coded offsets into the PE file format to discern
if a binary is PE32 or PE32+, and then to determine if it contains
a "CLR Data Directory" entry that looks valid.

Tested with PE32 and PE32+ compiled Mono binaries, PE32 and PE32+ native
binaries, and a random assortment of garbage files.

Former-commit-id: 9e7ac86ec84f653a2f79b87183efd5b0ebda001b
2023-10-16 20:16:47 +02:00

6285 lines
180 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//-------------------------------------------------------------
// <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
}
}