|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: BarChart.cs
//
// Namespace: DataVisualization.Charting.ChartTypes
//
// Classes: BarChart, RangeBarChart
//
// Purpose: Provides 2D/3D drawing and hit testing functionality
// for the Bar and RangeBar charts.
//
// Reviewed: AG - August 1, 2002
// AG - Microsoft 6, 2007
//
//===================================================================
#region Used namespaces
using System;
using System.Resources;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
#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;
#else
using System.Web.UI.DataVisualization.Charting;
using System.Web.UI.DataVisualization.Charting.Data;
using System.Web.UI.DataVisualization.Charting.ChartTypes;
using System.Web.UI.DataVisualization.Charting.Utilities;
#endif
#endregion
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes
#else
namespace System.Web.UI.DataVisualization.Charting.ChartTypes
#endif
{
#region Bar label style enumeration
/// <summary>
/// Bar chart value label drawing style.
/// </summary>
internal enum BarValueLabelDrawingStyle
{
/// <summary>
/// Outside of the bar.
/// </summary>
Outside,
/// <summary>
/// Inside the bar aligned to the left.
/// </summary>
Left,
/// <summary>
/// Inside the bar aligned to the center.
/// </summary>
Center,
/// <summary>
/// Inside the bar aligned to the right.
/// </summary>
Right,
};
#endregion
/// <summary>
/// BarChart class contains all the code necessary to draw
/// both Bar and RangeBar charts. The RangeBarChart class is used
/// to override few default settings, so that 2 Y values
/// will be used to define left and right position of each bar.
/// </summary>
internal class RangeBarChart : BarChart
{
#region Constructor
/// <summary>
/// Public constructor
/// </summary>
public RangeBarChart()
{
// Set the flag to use two Y values, while drawing the bars
this.useTwoValues = true;
// Change default style of the labels
this.defLabelDrawingStyle = BarValueLabelDrawingStyle.Center;
}
#endregion
#region IChartType interface implementation
/// <summary>
/// Chart type name
/// </summary>
override public string Name { get{ return ChartTypeNames.RangeBar;}}
/// <summary>
/// If the crossing value is auto Crossing value should be
/// automatically set to zero for some chart
/// types (Bar, column, area etc.)
/// </summary>
override public bool ZeroCrossing { get{ return true;} }
/// <summary>
/// Number of supported Y value(s) per point
/// </summary>
override public int YValuesPerPoint{ get { return 2; } }
/// <summary>
/// Indicates that extra Y values are connected to the scale of the Y axis
/// </summary>
override public bool ExtraYValuesConnectedToYAxis{ get { return true; } }
#endregion
}
/// <summary>
/// BarChart class provides 2D/3D drawing and hit testing
/// functionality for the Bar and RangeBar charts. The only
/// difference between the RangeBar and Bar chart is that
/// 2 Y values are used to position left and right side
/// of each RangeBar bar.
/// </summary>
internal class BarChart : IChartType
{
#region Fields
/// <summary>
/// Indicates that two Y values are used to calculate bar position
/// </summary>
protected bool useTwoValues = false;
/// <summary>
/// Indicates that bars from different series are drawn side by side
/// </summary>
protected bool drawSeriesSideBySide = true;
/// <summary>
/// Defines the default drawing style of the labels.
/// </summary>
protected BarValueLabelDrawingStyle defLabelDrawingStyle = BarValueLabelDrawingStyle.Outside;
/// <summary>
/// Indicates that second point loop is required to draw points labels or markers.
/// </summary>
protected bool pointLabelsMarkersPresent = false;
#endregion
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public BarChart()
{
}
#endregion
#region IChartType interface implementation
/// <summary>
/// Chart type name
/// </summary>
virtual public string Name { get{ return ChartTypeNames.Bar;}}
/// <summary>
/// Gets chart type image.
/// </summary>
/// <param name="registry">Chart types registry object.</param>
/// <returns>Chart type image.</returns>
virtual public System.Drawing.Image GetImage(ChartTypeRegistry registry)
{
return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
}
/// <summary>
/// True if chart type is stacked
/// </summary>
public bool Stacked { get{ return false;}}
/// <summary>
/// True if stacked chart type supports groups
/// </summary>
virtual public bool SupportStackedGroups { get { return false; } }
/// <summary>
/// True if stacked chart type should draw separately positive and
/// negative data points ( Bar and column Stacked types ).
/// </summary>
public bool StackSign { get{ return false;}}
/// <summary>
/// True if chart type supports axeses
/// </summary>
public bool RequireAxes { get{ return true;} }
/// <summary>
/// Chart type with two y values used for scale ( bubble chart type )
/// </summary>
public bool SecondYScale{ get{ return false;} }
/// <summary>
/// True if chart type requires circular chart area.
/// </summary>
public bool CircularChartArea { get{ return false;} }
/// <summary>
/// True if chart type supports Logarithmic axes
/// </summary>
public bool SupportLogarithmicAxes { get{ return true;} }
/// <summary>
/// True if chart type requires to switch the value (Y) axes position
/// </summary>
public bool SwitchValueAxes { get{ return true;} }
/// <summary>
/// True if chart series can be placed side-by-side.
/// </summary>
virtual public bool SideBySideSeries { get{ return true;} }
/// <summary>
/// If the crossing value is auto Crossing value should be
/// automatically set to zero for some chart
/// types (Bar, column, area etc.)
/// </summary>
virtual public bool ZeroCrossing { get{ return true;} }
/// <summary>
/// True if each data point of a chart must be represented in the legend
/// </summary>
public bool DataPointsInLegend { get{ return false;} }
/// <summary>
/// Indicates that extra Y values are connected to the scale of the Y axis
/// </summary>
virtual public bool ExtraYValuesConnectedToYAxis{ get { return false; } }
/// <summary>
/// Indicates that it's a hundredred percent chart.
/// Axis scale from 0 to 100 percent should be used.
/// </summary>
virtual public bool HundredPercent{ get{return false;} }
/// <summary>
/// Indicates that it's a hundredred percent chart.
/// Axis scale from 0 to 100 percent should be used.
/// </summary>
virtual public bool HundredPercentSupportNegative{ get{return false;} }
/// <summary>
/// True if palette colors should be applied for each data paoint.
/// Otherwise the color is applied to the series.
/// </summary>
public bool ApplyPaletteColorsToPoints { get { return false; } }
/// <summary>
/// How to draw series/points in legend:
/// Filled rectangle, Line or Marker
/// </summary>
/// <param name="series">Legend item series.</param>
/// <returns>Legend item style.</returns>
public LegendImageStyle GetLegendImageStyle(Series series)
{
return LegendImageStyle.Rectangle;
}
/// <summary>
/// Number of supported Y value(s) per point
/// </summary>
virtual public int YValuesPerPoint{ get { return 1; } }
#endregion
#region Painting and selection methods
/// <summary>
/// Paint Bar Chart.
/// </summary>
/// <param name="graph">The Chart Graphics object.</param>
/// <param name="common">The Common elements object.</param>
/// <param name="area">Chart area for this chart.</param>
/// <param name="seriesToDraw">Chart series to draw.</param>
public void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )
{
// Draw data points
this.pointLabelsMarkersPresent = false;
ProcessChartType( false, false, graph, common, area, seriesToDraw );
// Draw markers and lables
if(this.pointLabelsMarkersPresent)
{
ProcessChartType( true, false, graph, common, area, seriesToDraw );
}
}
/// <summary>
/// Calculates position of each bar in all series and either draws it or checks the selection.
/// </summary>
/// <param name="labels">Mode which draws only labels and markers.</param>
/// <param name="selection">If True selection mode is active, otherwise paint mode is active.</param>
/// <param name="graph">The Chart Graphics object.</param>
/// <param name="common">The Common elements object.</param>
/// <param name="area">Chart area for this chart.</param>
/// <param name="seriesToDraw">Chart series to draw.</param>
private void ProcessChartType(
bool labels,
bool selection,
ChartGraphics graph,
CommonElements common,
ChartArea area,
Series seriesToDraw )
{
//************************************************************
//** Local variables declaration
//************************************************************
int seriesIndx = 0; // Data Series index
bool sameInterval = false; // Series points are equally spaced
// Get pixel size
SizeF pixelRelSize = graph.GetRelativeSize(new SizeF(1.1f, 1.1f));
//************************************************************
//** Prosess 3D chart type
//************************************************************
if(area.Area3DStyle.Enable3D)
{
ProcessChartType3D( selection, graph, common, area, seriesToDraw );
return;
}
//************************************************************
//** Collect initial series data
//************************************************************
// All data series from chart area which have Bar chart type
List<string> typeSeries = area.GetSeriesFromChartType(Name);
// Check if series should be drawn side by side
bool currentDrawSeriesSideBySide = this.drawSeriesSideBySide;
foreach(string seriesName in typeSeries)
{
if(common.DataManager.Series[seriesName].IsCustomPropertySet(CustomPropertyName.DrawSideBySide))
{
string attribValue = common.DataManager.Series[seriesName][CustomPropertyName.DrawSideBySide];
if(String.Compare(attribValue, "False", StringComparison.OrdinalIgnoreCase ) == 0)
{
currentDrawSeriesSideBySide = false;
}
else if (String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase) == 0)
{
currentDrawSeriesSideBySide = true;
}
else if (String.Compare(attribValue, "Auto", StringComparison.OrdinalIgnoreCase) == 0)
{
// Do nothing
}
else
{
throw (new InvalidOperationException(SR.ExceptionAttributeDrawSideBySideInvalid));
}
}
}
// Find the number of "Bar chart" data series
int numOfSeries = typeSeries.Count;
if(!currentDrawSeriesSideBySide)
{
numOfSeries = 1;
}
// Check if bar chart series are indexed
bool indexedSeries = ChartHelper.IndexedSeries(area.Common, typeSeries.ToArray());
//************************************************************
//** Loop through all series
//************************************************************
foreach( Series ser in common.DataManager.Series )
{
// Process non empty series of the area with Bar chart type
if( String.Compare( ser.ChartTypeName, Name, true, System.Globalization.CultureInfo.CurrentCulture ) != 0
|| ser.ChartArea != area.Name || ser.Points.Count == 0 || !ser.IsVisible())
{
continue;
}
// Set active horizontal axis
Axis vAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
double vertViewMax = vAxis.ViewMaximum;
double vertViewMin = vAxis.ViewMinimum;
// Set active vertical axis
Axis hAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
double horizViewMax = hAxis.ViewMaximum;
double horizViewMin = hAxis.ViewMinimum;
// Get points interval:
// - set interval to 1 for indexed series
// - if points are not equaly spaced, the minimum interval between points is selected.
// - if points have same interval bars do not overlap each other.
double interval = 1;
if(!indexedSeries)
{
if(ser.Points.Count == 1 &&
(ser.XValueType == ChartValueType.Date ||
ser.XValueType == ChartValueType.DateTime ||
ser.XValueType == ChartValueType.Time ||
ser.XValueType == ChartValueType.DateTimeOffset))
{
// Check if interval is the same
area.GetPointsInterval( typeSeries, vAxis.IsLogarithmic, vAxis.logarithmBase, true, out sameInterval );
// Special case when there is only one data point and date scale is used.
if(!double.IsNaN(vAxis.majorGrid.GetInterval()) && vAxis.majorGrid.GetIntervalType() != DateTimeIntervalType.NotSet)
{
interval = ChartHelper.GetIntervalSize(vAxis.minimum, vAxis.majorGrid.GetInterval(), vAxis.majorGrid.GetIntervalType());
}
else
{
interval = ChartHelper.GetIntervalSize(vAxis.minimum, vAxis.Interval, vAxis.IntervalType);
}
}
else
{
interval = area.GetPointsInterval( typeSeries, vAxis.IsLogarithmic, vAxis.logarithmBase, true, out sameInterval );
}
}
// Calculates the width of bars.
double width = ser.GetPointWidth(graph, vAxis, interval, 0.8) / numOfSeries;
// Call Back Paint event
if( !selection )
{
common.Chart.CallOnPrePaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
}
//************************************************************
//** Loop through all points in series
//************************************************************
int pointIndex = 0;
int markerIndex = 0;
foreach( DataPoint point in ser.Points )
{
// Check required Y values number
if(point.YValues.Length < this.YValuesPerPoint)
{
throw(new InvalidOperationException(SR.ExceptionChartTypeRequiresYValues(this.Name, this.YValuesPerPoint.ToString(CultureInfo.InvariantCulture) )));
}
// Reset pre-calculated point position
point.positionRel = new PointF(float.NaN, float.NaN);
// Get Y value and make sure it fits the chart area.
// If chart type uses 2 Y values (RangeBar) use second Y value for size.
double yValue = hAxis.GetLogValue( GetYValue(common, area, ser, point, pointIndex, (useTwoValues) ? 1 : 0) );
bool yValueOutside = false;
bool yValueStartOutside = true;
if( (decimal)yValue > (decimal)horizViewMax )
{
yValue = horizViewMax;
yValueOutside = true;
}
else if( (decimal)yValue < (decimal)horizViewMin )
{
yValue = horizViewMin;
yValueOutside = true;
}
// Calculate the bar size
double barSize = hAxis.GetLinearPosition( yValue );
// Set start position for a bar
double barStartPosition = 0;
if(useTwoValues)
{
// Point Y value (first) is used to determine the bar starting position
double yValueStart = hAxis.GetLogValue( GetYValue(common, area, ser, point, pointIndex, 0 ) );
yValueStartOutside = false;
if( (decimal)yValueStart > (decimal)horizViewMax )
{
yValueStart = horizViewMax;
yValueStartOutside = true;
}
else if( (decimal)yValueStart < (decimal)horizViewMin )
{
yValueStart = horizViewMin;
yValueStartOutside = true;
}
barStartPosition = hAxis.GetLinearPosition(yValueStart);
}
else
{
// Bar starts on the vertical axis
barStartPosition = hAxis.GetPosition(hAxis.Crossing);
}
// Calculate X position of the Bar
double xPosition = 0;
if( indexedSeries )
{
// The formula for position is based on a distance
// from the grid line or nPoints position.
xPosition = vAxis.GetPosition( (double)pointIndex + 1 ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width;
}
else if( sameInterval )
{
xPosition = vAxis.GetPosition( point.XValue ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width;
}
else
{
xPosition = vAxis.GetPosition( point.XValue );
}
// Make sure that points with small values are still visible
if( barSize < barStartPosition &&
(barStartPosition - barSize) < pixelRelSize.Width)
{
barSize = barStartPosition - pixelRelSize.Width;
}
if( barSize > barStartPosition &&
(barSize - barStartPosition) < pixelRelSize.Width)
{
barSize = barStartPosition + pixelRelSize.Width;
}
// Set rectangle coordinates of the bar
RectangleF rectSize = RectangleF.Empty;
try
{
// Set the bar rectangle
rectSize.Y = (float)(xPosition - width/2);
rectSize.Height = (float)(width);
// The left side of rectangle has always
// smaller value than a right value
if( barStartPosition < barSize )
{
rectSize.X = (float)barStartPosition;
rectSize.Width = (float)barSize - rectSize.X;
}
else
{
rectSize.X = (float)barSize;
rectSize.Width = (float)barStartPosition - rectSize.X;
}
}
catch(OverflowException)
{
pointIndex++;
continue;
}
// Remeber pre-calculated point position
point.positionRel = new PointF(( barStartPosition < barSize ) ? rectSize.Right : rectSize.X, (float)xPosition);
//************************************************************
//** Painting mode
//************************************************************
if( common.ProcessModePaint )
{
// if data point is not empty and not labels drawing mode
if( !point.IsEmpty && !labels)
{
// Check if column is completly out of the data scaleView
double xValue = (indexedSeries) ? pointIndex + 1 : point.XValue;
xValue = vAxis.GetLogValue(xValue);
if(xValue < vertViewMin || xValue > vertViewMax )
{
pointIndex++;
continue;
}
// Check if column is partialy in the data scaleView
bool clipRegionSet = false;
if(rectSize.Y < area.PlotAreaPosition.Y || rectSize.Bottom > area.PlotAreaPosition.Bottom)
{
// Set clipping region for line drawing
graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
clipRegionSet = true;
}
// Start Svg Selection mode
graph.StartHotRegion( point );
// Draw the bar rectangle
graph.FillRectangleRel( rectSize,
point.Color,
point.BackHatchStyle,
point.BackImage,
point.BackImageWrapMode,
point.BackImageTransparentColor,
point.BackImageAlignment,
point.BackGradientStyle,
point.BackSecondaryColor,
point.BorderColor,
point.BorderWidth,
point.BorderDashStyle,
ser.ShadowColor,
ser.ShadowOffset,
PenAlignment.Inset,
ChartGraphics.GetBarDrawingStyle(point),
false);
// End Svg Selection mode
graph.EndHotRegion( );
// Reset Clip Region
if(clipRegionSet)
{
graph.ResetClip();
}
if (common.ProcessModeRegions)
{
common.HotRegionsList.AddHotRegion(rectSize, point, ser.Name, pointIndex);
}
}
// Draw labels and markers (only if part of the bar is visible)
if( !(yValueOutside && yValueStartOutside && rectSize.Width == 0f) )
{
if ((rectSize.Y + rectSize.Height / 2f) >= area.PlotAreaPosition.Y &&
(rectSize.Y + rectSize.Height / 2f) <= area.PlotAreaPosition.Bottom)
{
if(labels)
{
DrawLabelsAndMarkers( area, graph, common, rectSize, point, ser, barStartPosition, barSize, width, pointIndex, ref markerIndex);
}
else
{
// Check if separate drawing loop required for labels and markers
if(point.MarkerStyle != MarkerStyle.None || point.MarkerImage.Length > 0)
{
this.pointLabelsMarkersPresent = true;
}
else if(ser.IsValueShownAsLabel || point.IsValueShownAsLabel || point.Label.Length > 0)
{
this.pointLabelsMarkersPresent = true;
}
}
}
}
}
//************************************************************
// Hot Regions mode used for image maps, tool tips and
// hit test function
//************************************************************
if (common.ProcessModeRegions && !common.ProcessModePaint)
{
common.HotRegionsList.AddHotRegion( rectSize, point, ser.Name, pointIndex );
// Process labels and markers regions only if it was not done while painting
if(labels )
{
DrawLabelsAndMarkers( area, graph, common, rectSize, point, ser, barStartPosition, barSize, width, pointIndex, ref markerIndex);
}
}
// Increase the data index counter
pointIndex++;
}
// Call Paint event
if( !selection )
{
common.Chart.CallOnPostPaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
}
// Increase data series index
if(currentDrawSeriesSideBySide)
{
seriesIndx++;
}
}
}
/// <summary>
/// Adjusts size in pixels to the DPIs different than 96.
/// </summary>
/// <param name="pixelSize">Size in pixels.</param>
/// <param name="graph">Chart graphics.</param>
/// <returns>Adjusted pixel size.</returns>
private static int GetAdjustedPixelSize(int pixelSize, ChartGraphics graph)
{
if (graph != null && graph.Graphics != null)
{
// Marker size is in pixels and we do the mapping for higher DPIs
SizeF size = new SizeF();
size.Width = pixelSize * graph.Graphics.DpiX / 96;
size.Height = pixelSize * graph.Graphics.DpiY / 96;
pixelSize = (int)Math.Max(size.Width, size.Height);
}
return pixelSize;
}
/// <summary>
/// Draws labels and markers.
/// </summary>
/// <param name="area">Chart area for this chart.</param>
/// <param name="graph">The Chart Graphics object.</param>
/// <param name="common">The Common elements object.</param>
/// <param name="rectSize">Bar rectangle.</param>
/// <param name="point">Data point.</param>
/// <param name="ser">Data series.</param>
/// <param name="barStartPosition">The zero position or the bottom of bars.</param>
/// <param name="barSize">The Height of bars.</param>
/// <param name="width">The width of bars.</param>
/// <param name="pointIndex">Point index.</param>
/// <param name="markerIndex">Marker index reference.</param>
private void DrawLabelsAndMarkers(
ChartArea area,
ChartGraphics graph,
CommonElements common,
RectangleF rectSize,
DataPoint point,
Series ser,
double barStartPosition,
double barSize,
double width,
int pointIndex,
ref int markerIndex)
{
//************************************************************
// Draw data point value marker
//************************************************************
SizeF markerSize = SizeF.Empty;
if(point.MarkerStyle != MarkerStyle.None || point.MarkerImage.Length > 0)
{
// Check if this marker should be drawn
if(markerIndex == 0)
{
// Find relative marker size
if(point.MarkerImage.Length == 0)
{
markerSize.Width = point.MarkerSize;
markerSize.Height = point.MarkerSize;
}
else
common.ImageLoader.GetAdjustedImageSize(point.MarkerImage, graph.Graphics, ref markerSize);
markerSize = graph.GetRelativeSize( markerSize );
// Calculate marker position
PointF markerPosition = PointF.Empty;
if( barStartPosition < barSize )
{
markerPosition.X = rectSize.Right;
}
else
{
markerPosition.X = rectSize.X;
}
markerPosition.Y = rectSize.Y + rectSize.Height/2F;
if( common.ProcessModePaint )
{
// Draw marker
graph.DrawMarkerRel(
markerPosition,
point.MarkerStyle,
BarChart.GetAdjustedPixelSize(point.MarkerSize, graph),
(point.MarkerColor == Color.Empty) ? point.Color : point.MarkerColor,
point.MarkerBorderColor,
point.MarkerBorderWidth,
point.MarkerImage,
point.MarkerImageTransparentColor,
(point.series != null) ? point.series.ShadowOffset : 0,
(point.series != null) ? point.series.ShadowColor : Color.Empty,
RectangleF.Empty);
}
if( common.ProcessModeRegions )
{
SetHotRegions(
common,
graph,
point,
markerSize,
point.series.Name,
pointIndex,
point.MarkerStyle,
markerPosition );
}
}
// Increase the markers counter
++markerIndex;
if(ser.MarkerStep == markerIndex)
{
markerIndex = 0;
}
}
//************************************************************
// Draw data point value label
//************************************************************
if (point.Label.Length > 0 ||
(!point.IsEmpty && (ser.IsValueShownAsLabel || point.IsValueShownAsLabel)))
{
// Label rectangle
RectangleF rectLabel = RectangleF.Empty;
// Label text format
using (StringFormat format = new StringFormat())
{
//************************************************************
// Get label text
//************************************************************
string text;
if( point.Label.Length == 0 )
{
text = ValueConverter.FormatValue(
ser.Chart,
point,
point.Tag,
GetYValue(common, area, ser, point, pointIndex, 0 ),
point.LabelFormat,
ser.YValueType,
ChartElementType.DataPoint);
}
else
{
text = point.ReplaceKeywords(point.Label);
}
//************************************************************
// Check labels style custom properties
//************************************************************
BarValueLabelDrawingStyle drawingStyle = defLabelDrawingStyle;
string valueLabelAttrib = "";
if (point.IsCustomPropertySet(CustomPropertyName.BarLabelStyle))
{
valueLabelAttrib = point[CustomPropertyName.BarLabelStyle];
}
else if (ser.IsCustomPropertySet(CustomPropertyName.BarLabelStyle))
{
valueLabelAttrib = ser[CustomPropertyName.BarLabelStyle];
}
if (valueLabelAttrib.Length > 0)
{
if (String.Compare(valueLabelAttrib, "Left", StringComparison.OrdinalIgnoreCase) == 0)
drawingStyle = BarValueLabelDrawingStyle.Left;
if (String.Compare(valueLabelAttrib, "Right", StringComparison.OrdinalIgnoreCase) == 0)
drawingStyle = BarValueLabelDrawingStyle.Right;
if (String.Compare(valueLabelAttrib, "Center", StringComparison.OrdinalIgnoreCase) == 0)
drawingStyle = BarValueLabelDrawingStyle.Center;
else if (String.Compare(valueLabelAttrib, "Outside", StringComparison.OrdinalIgnoreCase) == 0)
drawingStyle = BarValueLabelDrawingStyle.Outside;
}
//************************************************************
// Make sure label fits. Otherwise change it style
//************************************************************
bool labelFit = false;
bool labelSwitched = false;
bool labelSwitchedBack = false;
float prevWidth = 0f;
while (!labelFit)
{
// LabelStyle text format
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Center;
// Label rectangle
if (barStartPosition <= barSize)
{
rectLabel.X = rectSize.Right;
rectLabel.Width = area.PlotAreaPosition.Right - rectSize.Right;
if (rectLabel.Width < 0.001f &&
barStartPosition == barSize)
{
rectLabel.Width = rectSize.X - area.PlotAreaPosition.X;
rectLabel.X = area.PlotAreaPosition.X;
format.Alignment = StringAlignment.Far;
}
}
else
{
rectLabel.X = area.PlotAreaPosition.X;
rectLabel.Width = rectSize.X - area.PlotAreaPosition.X;
}
// Adjust label rectangle
rectLabel.Y = rectSize.Y - (float)width / 2F;
rectLabel.Height = rectSize.Height + (float)width;
// Adjust label position depending on the drawing style
if (drawingStyle == BarValueLabelDrawingStyle.Outside)
{
// Adjust position if point marker is drawn
if (!markerSize.IsEmpty)
{
rectLabel.Width -= (float)Math.Min(rectLabel.Width, markerSize.Width / 2F);
if (barStartPosition < barSize)
{
rectLabel.X += (float)Math.Min(rectLabel.Width, markerSize.Width / 2F);
}
}
}
else if (drawingStyle == BarValueLabelDrawingStyle.Left)
{
rectLabel = rectSize;
format.Alignment = StringAlignment.Near;
}
else if (drawingStyle == BarValueLabelDrawingStyle.Center)
{
rectLabel = rectSize;
format.Alignment = StringAlignment.Center;
}
else if (drawingStyle == BarValueLabelDrawingStyle.Right)
{
rectLabel = rectSize;
format.Alignment = StringAlignment.Far;
// Adjust position if point marker is drawn
if (!markerSize.IsEmpty)
{
rectLabel.Width -= (float)Math.Min(rectLabel.Width, markerSize.Width / 2F);
if (barStartPosition >= barSize)
{
rectLabel.X += (float)Math.Min(rectLabel.Width, markerSize.Width / 2F);
}
}
}
// Reversed string alignment
if (barStartPosition > barSize)
{
if (format.Alignment == StringAlignment.Far)
format.Alignment = StringAlignment.Near;
else if (format.Alignment == StringAlignment.Near)
format.Alignment = StringAlignment.Far;
}
// Make sure value label fits rectangle.
SizeF valueTextSize = graph.MeasureStringRel(text, point.Font);
if (!labelSwitched &&
!labelSwitchedBack &&
valueTextSize.Width > rectLabel.Width - 1)
{
// Switch label style only once
labelSwitched = true;
prevWidth = rectLabel.Width;
// If text do not fit - try to switch between Outside/Inside drawing styles
if (drawingStyle == BarValueLabelDrawingStyle.Outside)
{
drawingStyle = BarValueLabelDrawingStyle.Right;
}
else
{
drawingStyle = BarValueLabelDrawingStyle.Outside;
}
}
else
{
// If label do not fit either Outside or to the Right,
// select the style that has more space available.
if (labelSwitched &&
!labelSwitchedBack &&
valueTextSize.Width > rectLabel.Width - 1 &&
prevWidth > rectLabel.Width)
{
labelSwitchedBack = true;
// Change back to the previous labels style
if (drawingStyle == BarValueLabelDrawingStyle.Outside)
{
drawingStyle = BarValueLabelDrawingStyle.Right;
}
else
{
drawingStyle = BarValueLabelDrawingStyle.Outside;
}
}
else
{
// Do not try to fit labels any more
labelFit = true;
}
}
}
//************************************************************
// Draw label
//************************************************************
// Calculate label background position
RectangleF labelBackPosition = RectangleF.Empty;
if (common.ProcessModeRegions ||
!point.LabelBackColor.IsEmpty ||
!point.LabelBorderColor.IsEmpty)
{
if (rectLabel.Width > 0 && rectLabel.Height > 0)
{
// Get label background position
SizeF valueTextSize = graph.MeasureStringRel(text, point.Font);
valueTextSize.Height += valueTextSize.Height / 8;
float spacing = valueTextSize.Width / text.Length / 2;
valueTextSize.Width += spacing;
labelBackPosition = new RectangleF(
rectLabel.X,
rectLabel.Y + (rectLabel.Height - valueTextSize.Height) / 2,
valueTextSize.Width,
valueTextSize.Height);
// Adjust position based on alignment
if (format.Alignment == StringAlignment.Near)
{
labelBackPosition.X += spacing / 2f;
rectLabel.X += spacing;
}
else if (format.Alignment == StringAlignment.Center)
{
labelBackPosition.X = rectLabel.X + (rectLabel.Width - valueTextSize.Width) / 2f;
}
else if (format.Alignment == StringAlignment.Far)
{
labelBackPosition.X = rectLabel.Right - valueTextSize.Width - spacing / 2f;
rectLabel.X -= spacing;
}
}
}
// Make sure there is enough vertical space for the label
// NOTE: Fixes issue #4502
SizeF textSize = graph.MeasureStringRel(text, point.Font);
if (textSize.Height > rectLabel.Height)
{
rectLabel.Y -= (textSize.Height - rectLabel.Height) / 2f;
rectLabel.Height = textSize.Height;
}
// Draw label text
using (Brush brush = new SolidBrush(point.LabelForeColor))
{
graph.DrawPointLabelStringRel(
common,
text,
point.Font,
brush,
rectLabel,
format,
point.LabelAngle,
labelBackPosition,
point.LabelBackColor,
point.LabelBorderColor,
point.LabelBorderWidth,
point.LabelBorderDashStyle,
ser,
point,
pointIndex);
}
}
}
}
/// <summary>
/// Inserts Hot Regions used for image maps, tool tips and
/// hit test function
/// </summary>
/// <param name="common">Common elements object</param>
/// <param name="graph">Chart Graphics object</param>
/// <param name="point">Data point used for hot region</param>
/// <param name="markerSize">Size of the marker</param>
/// <param name="seriesName">Name of the series</param>
/// <param name="pointIndex">Data point index</param>
/// <param name="pointMarkerStyle">Marker Style</param>
/// <param name="markerPosition">Marker Position</param>
private void SetHotRegions( CommonElements common, ChartGraphics graph, DataPoint point, SizeF markerSize, string seriesName, int pointIndex, MarkerStyle pointMarkerStyle, PointF markerPosition )
{
// Get relative marker size
SizeF relativeMarkerSize = markerSize;
int insertIndex = common.HotRegionsList.FindInsertIndex();
// Insert circle area
if( pointMarkerStyle == MarkerStyle.Circle )
{
common.HotRegionsList.AddHotRegion( insertIndex, graph, markerPosition.X, markerPosition.Y, relativeMarkerSize.Width/2f, point, seriesName, pointIndex );
}
// All other markers represented as rectangles
else
{
// Insert area
common.HotRegionsList.AddHotRegion(
new RectangleF(markerPosition.X - relativeMarkerSize.Width/2f, markerPosition.Y - relativeMarkerSize.Height/2f, relativeMarkerSize.Width, relativeMarkerSize.Height),
point,
seriesName,
pointIndex );
}
}
#endregion
#region Getting Y value methods
/// <summary>
/// Helper function, which returns the Y value of the point.
/// </summary>
/// <param name="common">Chart common elements.</param>
/// <param name="area">Chart area the series belongs to.</param>
/// <param name="series">Sereis of the point.</param>
/// <param name="point">Point object.</param>
/// <param name="pointIndex">Index of the point.</param>
/// <param name="yValueIndex">Index of the Y value to get.</param>
/// <returns>Y value of the point.</returns>
virtual public double GetYValue(
CommonElements common,
ChartArea area,
Series series,
DataPoint point,
int pointIndex,
int yValueIndex)
{
// Point chart do not have height
if(yValueIndex == -1)
{
return 0.0;
}
// Check required Y values number
if (point.YValues.Length <= yValueIndex)
{
throw (new InvalidOperationException(SR.ExceptionChartTypeRequiresYValues(this.Name, this.YValuesPerPoint.ToString(CultureInfo.InvariantCulture))));
}
if (point.IsEmpty || double.IsNaN(point.YValues[yValueIndex]))
{
// Get empty point value
double result = GetEmptyPointValue(point, pointIndex, yValueIndex);
// NOTE: Fixes issue #6921
// If empty point Y value is zero then check if the scale of
// the Y axis and if it is not containing zero adjust the Y value
// of the empty point, so it will be visible
if (result == 0.0)
{
Axis yAxis = area.GetAxis(AxisName.Y, series.YAxisType, series.YSubAxisName);
double yViewMax = yAxis.maximum;
double yViewMin = yAxis.minimum;
if (result < yViewMin)
{
result = yViewMin;
}
else if (result > yViewMax)
{
result = yViewMax;
}
}
return result;
}
// Return Y value from the point
return point.YValues[yValueIndex];
}
/// <summary>
/// This method will find previous and next data point, which is not
/// empty and recalculate a new value for current empty data point.
/// New value depends on custom attribute “EmptyPointValue” and
/// it could be zero or average.
/// </summary>
/// <param name="point">IsEmpty data point.</param>
/// <param name="pointIndex">IsEmpty data point index.</param>
/// <param name="yValueIndex">Index of the Y value to get.</param>
/// <returns>A Value for empty data point.</returns>
internal double GetEmptyPointValue( DataPoint point, int pointIndex, int yValueIndex)
{
Series series = point.series; // Data series
double previousPoint = 0; // Previous data point value (not empty)
double nextPoint = 0; // Next data point value (not empty)
int prevIndx = 0; // Previous data point index
int nextIndx = series.Points.Count - 1; // Next data point index
//************************************************************
//** Check custom attribute "EmptyPointValue"
//************************************************************
string emptyPointValue = "";
if( series.EmptyPointStyle.IsCustomPropertySet(CustomPropertyName.EmptyPointValue) )
{
emptyPointValue = series.EmptyPointStyle[CustomPropertyName.EmptyPointValue];
}
else if( series.IsCustomPropertySet(CustomPropertyName.EmptyPointValue) )
{
emptyPointValue = series[CustomPropertyName.EmptyPointValue];
}
// Take attribute value
if (String.Compare(emptyPointValue, "Zero", StringComparison.OrdinalIgnoreCase) == 0)
{
// IsEmpty points represented with zero values
return 0;
}
//************************************************************
//** IsEmpty point value is an average of neighbour points
//************************************************************
// Find previous non-empty point value
for( int indx = pointIndex; indx >= 0; indx-- )
{
if( !series.Points[indx].IsEmpty )
{
previousPoint = series.Points[indx].YValues[yValueIndex];
prevIndx = indx;
break;
}
previousPoint = Double.NaN;
}
// Find next non-empty point value
for( int indx = pointIndex; indx < series.Points.Count; indx++ )
{
if( !series.Points[indx].IsEmpty )
{
nextPoint = series.Points[indx].YValues[yValueIndex];
nextIndx = indx;
break;
}
nextPoint = Double.NaN;
}
// All Previous points are empty
if( Double.IsNaN( previousPoint ) )
{
// All points are empty
if( Double.IsNaN( nextPoint ) )
{
previousPoint = 0;
}
else // Next point is equal to previous point
{
previousPoint = nextPoint;
}
}
// All next points are empty
if( Double.IsNaN( nextPoint ) )
{
// Previous point is equal to next point
nextPoint = previousPoint;
}
// If points value are the same use average
if( series.Points[nextIndx].XValue == series.Points[prevIndx].XValue )
{
return ( previousPoint + nextPoint ) / 2;
}
// Calculate and return average value
double aCoeff = (previousPoint - nextPoint) / (series.Points[nextIndx].XValue - series.Points[prevIndx].XValue);
return -aCoeff * (point.XValue - series.Points[prevIndx].XValue) + previousPoint;
}
#endregion
#region 3D Drawing and Selection
/// <summary>
/// Calculates position of each bar in all series and either draws it or checks the selection in 3D space.
/// </summary>
/// <param name="selection">If True selection mode is active, otherwise paint mode is active.</param>
/// <param name="graph">The Chart Graphics object.</param>
/// <param name="common">The Common elements object.</param>
/// <param name="area">Chart area for this chart.</param>
/// <param name="seriesToDraw">Chart series to draw.</param>
private void ProcessChartType3D(
bool selection,
ChartGraphics graph,
CommonElements common,
ChartArea area,
Series seriesToDraw )
{
//************************************************************
//** Local variables declaration
//************************************************************
// Get pixel size
SizeF pixelRelSize = graph.GetRelativeSize(new SizeF(1.1f, 1.1f));
//************************************************************
//** Get list of series to draw
//************************************************************
double xValue = 0;
List<string> typeSeries = null;
bool currentDrawSeriesSideBySide = this.drawSeriesSideBySide;
if( (area.Area3DStyle.IsClustered && this.SideBySideSeries) ||
this.Stacked)
{
// Draw all series of the same chart type
typeSeries = area.GetSeriesFromChartType(Name);
// Check if series should be drawn side by side
foreach(string seriesName in typeSeries)
{
if(common.DataManager.Series[seriesName].IsCustomPropertySet(CustomPropertyName.DrawSideBySide))
{
string attribValue = common.DataManager.Series[seriesName][CustomPropertyName.DrawSideBySide];
if(String.Compare(attribValue, "False", StringComparison.OrdinalIgnoreCase) == 0)
{
currentDrawSeriesSideBySide = false;
}
else if(String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase) == 0)
{
currentDrawSeriesSideBySide = true;
}
else if(String.Compare(attribValue, "Auto", StringComparison.OrdinalIgnoreCase) == 0)
{
// Do nothing
}
else
{
throw (new InvalidOperationException(SR.ExceptionAttributeDrawSideBySideInvalid));
}
}
}
}
else
{
// Draw just one chart series
typeSeries = new List<string>();
typeSeries.Add(seriesToDraw.Name);
}
//************************************************************
//** Get order of data points drawing
//************************************************************
ArrayList dataPointDrawingOrder = area.GetDataPointDrawingOrder(
typeSeries,
this,
selection,
COPCoordinates.X | COPCoordinates.Y,
new BarPointsDrawingOrderComparer(area, selection, COPCoordinates.X | COPCoordinates.Y),
0,
currentDrawSeriesSideBySide);
//************************************************************
//** Loop through all data poins
//************************************************************
bool drawLabels = false;
foreach(object obj in dataPointDrawingOrder)
{
// Get point & series
DataPoint3D pointEx = (DataPoint3D) obj;
DataPoint point = pointEx.dataPoint;
Series ser = point.series;
// Check required Y values number
if(point.YValues.Length < this.YValuesPerPoint)
{
throw(new InvalidOperationException(SR.ExceptionChartTypeRequiresYValues(this.Name, this.YValuesPerPoint.ToString(CultureInfo.InvariantCulture))));
}
// Reset pre-calculated point position
point.positionRel = new PointF(float.NaN, float.NaN);
// Set active vertical/horizontal axis
Axis vAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
Axis hAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
// Get point bar drawing style
BarDrawingStyle barDrawingStyle = ChartGraphics.GetBarDrawingStyle(point);
// Get Y value and make sure it fits the chart area.
// If chart type uses 2 Y values (RangeBar) use second Y value for size.
float rightDarkening = 0f;
float leftDarkening = 0f;
double yValue = hAxis.GetLogValue( GetYValue(common, area, ser, pointEx.dataPoint, pointEx.index - 1, (useTwoValues) ? 1 : 0) );
if( yValue > hAxis.ViewMaximum )
{
rightDarkening = 0.5f;
yValue = hAxis.ViewMaximum;
}
else if( yValue < hAxis.ViewMinimum )
{
rightDarkening = 0.5f;
yValue = hAxis.ViewMinimum;
}
// Calculate the bar size
double barSize = hAxis.GetLinearPosition( yValue );
// Set start position for a bar
double barStartPosition = 0;
if(useTwoValues)
{
// Point Y value (first) is used to determine the bar starting position
double yValueStart = hAxis.GetLogValue( GetYValue(common, area, ser, pointEx.dataPoint, pointEx.index - 1, 0 ) );
if( yValueStart > hAxis.ViewMaximum )
{
leftDarkening = 0.5f;
yValueStart = hAxis.ViewMaximum;
}
else if( yValueStart < hAxis.ViewMinimum )
{
leftDarkening = 0.5f;
yValueStart = hAxis.ViewMinimum;
}
barStartPosition = hAxis.GetLinearPosition(yValueStart);
}
else
{
// Bar starts on the vertical axis
barStartPosition = hAxis.GetPosition(hAxis.Crossing);
}
// Calculate X position of the Bar
double xPosition = pointEx.xPosition;
// Make sure that points with small values are still visible
if( barSize < barStartPosition &&
(barStartPosition - barSize) < pixelRelSize.Width)
{
barSize = barStartPosition - pixelRelSize.Width;
}
if( barSize > barStartPosition &&
(barSize - barStartPosition) < pixelRelSize.Width)
{
barSize = barStartPosition + pixelRelSize.Width;
}
// Set rectangle coordinates of the bar
RectangleF rectSize = RectangleF.Empty;
try
{
// Set the bar rectangle
rectSize.Y = (float)(xPosition - pointEx.width/2);
rectSize.Height = (float)(pointEx.width);
// The left side of rectangle has always
// smaller value than a right value
if( barStartPosition < barSize )
{
rectSize.X = (float)barStartPosition;
rectSize.Width = (float)barSize - rectSize.X;
}
else
{
float temp = rightDarkening;
rightDarkening = leftDarkening;
leftDarkening = temp;
rectSize.X = (float)barSize;
rectSize.Width = (float)barStartPosition - rectSize.X;
}
}
catch(OverflowException)
{
continue;
}
// Remeber pre-calculated point position
point.positionRel = new PointF(rectSize.Right, (float)xPosition);
//************************************************************
//** Painting mode
//************************************************************
GraphicsPath rectPath = null;
// if data point is not empty
if( !point.IsEmpty )
{
// Check if column is completly out of the data scaleView
xValue = (pointEx.indexedSeries) ? pointEx.index : point.XValue;
xValue = vAxis.GetLogValue(xValue);
if(xValue < vAxis.ViewMinimum || xValue > vAxis.ViewMaximum )
{
continue;
}
// Check if column is partialy in the data scaleView
bool clipRegionSet = false;
if(rectSize.Bottom <= area.PlotAreaPosition.Y || rectSize.Y >= area.PlotAreaPosition.Bottom)
{
continue;
}
if(rectSize.Y < area.PlotAreaPosition.Y)
{
rectSize.Height -= area.PlotAreaPosition.Y - rectSize.Y;
rectSize.Y = area.PlotAreaPosition.Y;
}
if(rectSize.Bottom > area.PlotAreaPosition.Bottom)
{
rectSize.Height -= rectSize.Bottom - area.PlotAreaPosition.Bottom;
}
if(rectSize.Height < 0)
{
rectSize.Height = 0;
}
if(rectSize.Height == 0f || rectSize.Width == 0f)
{
continue;
}
// Detect if we need to get graphical path of drawn object
DrawingOperationTypes drawingOperationType = DrawingOperationTypes.DrawElement;
if( common.ProcessModeRegions )
{
drawingOperationType |= DrawingOperationTypes.CalcElementPath;
}
// Start Svg Selection mode
graph.StartHotRegion( point );
// Draw the bar rectangle
rectPath = graph.Fill3DRectangle(
rectSize,
pointEx.zPosition,
pointEx.depth,
area.matrix3D,
area.Area3DStyle.LightStyle,
point.Color,
rightDarkening,
leftDarkening,
point.BorderColor,
point.BorderWidth,
point.BorderDashStyle,
barDrawingStyle,
false,
drawingOperationType );
// End Svg Selection mode
graph.EndHotRegion( );
// Reset Clip Region
if(clipRegionSet)
{
graph.ResetClip();
}
}
// Draw 3D markers
DrawMarkers3D( area, graph, common, rectSize, pointEx, ser, barStartPosition, barSize );
// Check if labels should be drawn (in additional points loop)
if( point.IsValueShownAsLabel || point.Label.Length > 0 )
{
drawLabels = true;
}
//************************************************************
// Hot Regions mode used for image maps, tool tips and
// hit test function
//************************************************************
if( common.ProcessModeRegions )
{
common.HotRegionsList.AddHotRegion(
rectPath,
false,
graph,
point,
ser.Name,
pointEx.index - 1 );
}
if (rectPath != null)
{
rectPath.Dispose();
}
}
//************************************************************
//** Loop through all data poins and draw labels
//************************************************************
if(drawLabels)
{
foreach(object obj in dataPointDrawingOrder)
{
// Get point & series
DataPoint3D pointEx = (DataPoint3D) obj;
DataPoint point = pointEx.dataPoint;
Series ser = point.series;
// Set active vertical/horizontal axis
Axis vAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
Axis hAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
// Get Y value and make sure it fits the chart area.
// If chart type uses 2 Y values (RangeBar) use second Y value for size.
double yValue = hAxis.GetLogValue( GetYValue(common, area, ser, pointEx.dataPoint, pointEx.index - 1, (useTwoValues) ? 1 : 0) );
if( yValue > hAxis.ViewMaximum )
{
yValue = hAxis.ViewMaximum;
}
else if( yValue < hAxis.ViewMinimum )
{
yValue = hAxis.ViewMinimum;
}
// Calculate the bar size
double barSize = hAxis.GetLinearPosition( yValue );
// Set start position for a bar
double barStartPosition = 0;
if(useTwoValues)
{
// Point Y value (first) is used to determine the bar starting position
double yValueStart = hAxis.GetLogValue( GetYValue(common, area, ser, pointEx.dataPoint, pointEx.index - 1, 0 ) );
if( yValueStart > hAxis.ViewMaximum )
{
yValueStart = hAxis.ViewMaximum;
}
else if( yValueStart < hAxis.ViewMinimum )
{
yValueStart = hAxis.ViewMinimum;
}
barStartPosition = hAxis.GetLinearPosition(yValueStart);
}
else
{
// Bar starts on the vertical axis
barStartPosition = hAxis.GetPosition(hAxis.Crossing);
}
// Calculate X position of the Bar
double xPosition = pointEx.xPosition;
// Set rectangle coordinates of the bar
RectangleF rectSize = RectangleF.Empty;
try
{
// Set the bar rectangle
rectSize.Y = (float)(xPosition - pointEx.width/2);
rectSize.Height = (float)(pointEx.width);
// The left side of rectangle has always
// smaller value than a right value
if( barStartPosition < barSize )
{
rectSize.X = (float)barStartPosition;
rectSize.Width = (float)barSize - rectSize.X;
}
else
{
rectSize.X = (float)barSize;
rectSize.Width = (float)barStartPosition - rectSize.X;
}
}
catch(OverflowException)
{
continue;
}
//************************************************************
//** Painting mode
//************************************************************
// if data point is not empty
if( !point.IsEmpty )
{
// Check if column is completly out of the data scaleView
xValue = (pointEx.indexedSeries) ? pointEx.index : point.XValue;
xValue = vAxis.GetLogValue(xValue);
if(xValue < vAxis.ViewMinimum || xValue > vAxis.ViewMaximum )
{
continue;
}
// Check if column is partialy in the data scaleView
if ((decimal)rectSize.Y >= (decimal)area.PlotAreaPosition.Y && (decimal)rectSize.Bottom <= (decimal)area.PlotAreaPosition.Bottom)
{
// Draw 3D labels
DrawLabels3D(area, graph, common, rectSize, pointEx, ser, barStartPosition, barSize, pointEx.width, pointEx.index - 1);
}
}
}
}
}
/// <summary>
/// Draws markers in 3D.
/// </summary>
/// <param name="area">Chart area for this chart.</param>
/// <param name="graph">The Chart Graphics object.</param>
/// <param name="common">The Common elements object.</param>
/// <param name="rectSize">Bar rectangle.</param>
/// <param name="pointEx">Data point.</param>
/// <param name="ser">Data series.</param>
/// <param name="barStartPosition">The zero position or the bottom of bars.</param>
/// <param name="barSize">The Height of bars.</param>
private void DrawMarkers3D(
ChartArea area,
ChartGraphics graph,
CommonElements common,
RectangleF rectSize,
DataPoint3D pointEx,
Series ser,
double barStartPosition,
double barSize)
{
DataPoint point = pointEx.dataPoint;
//************************************************************
// Draw data point value marker
//************************************************************
SizeF markerSize = SizeF.Empty;
if(point.MarkerStyle != MarkerStyle.None || point.MarkerImage.Length > 0)
{
// Check if this marker should be drawn
if((pointEx.index % ser.MarkerStep) == 0)
{
// Find relative marker size
if(point.MarkerImage.Length == 0)
{
markerSize.Width = point.MarkerSize;
markerSize.Height = point.MarkerSize;
}
else
common.ImageLoader.GetAdjustedImageSize(point.MarkerImage, graph.Graphics, ref markerSize);
markerSize = graph.GetRelativeSize( markerSize );
// Calculate marker position
PointF markerPosition = PointF.Empty;
if( barStartPosition < barSize )
{
markerPosition.X = rectSize.Right;
}
else
{
markerPosition.X = rectSize.X;
}
markerPosition.Y = rectSize.Y + rectSize.Height/2F;
//************************************************************
//** Transform marker position in 3D space
//************************************************************
// Get projection coordinates
Point3D[] marker3DPosition = new Point3D[1];
marker3DPosition[0] = new Point3D(markerPosition.X, markerPosition.Y, (float)(pointEx.zPosition + pointEx.depth/2f));
// Transform coordinates of text size
area.matrix3D.TransformPoints(marker3DPosition);
//************************************************************
//** Draw 3D marker
//************************************************************
graph.DrawMarker3D(
area.matrix3D,
area.Area3DStyle.LightStyle,
pointEx.zPosition + pointEx.depth/2f,
markerPosition,
point.MarkerStyle,
BarChart.GetAdjustedPixelSize(point.MarkerSize, graph),
(point.MarkerColor.IsEmpty) ? point.series.Color : point.MarkerColor,
point.MarkerBorderColor,
point.MarkerBorderWidth,
point.MarkerImage,
point.MarkerImageTransparentColor,
(point.series != null) ? point.series.ShadowOffset : 0,
(point.series != null) ? point.series.ShadowColor : Color.Empty,
RectangleF.Empty,
DrawingOperationTypes.DrawElement);
}
}
}
/// <summary>
/// Draws labels in 3D.
/// </summary>
/// <param name="area">Chart area for this chart.</param>
/// <param name="graph">The Chart Graphics object.</param>
/// <param name="common">The Common elements object.</param>
/// <param name="rectSize">Bar rectangle.</param>
/// <param name="pointEx">Data point.</param>
/// <param name="ser">Data series.</param>
/// <param name="barStartPosition">The zero position or the bottom of bars.</param>
/// <param name="barSize">The Height of bars.</param>
/// <param name="width">The width of bars.</param>
/// <param name="pointIndex">Point index.</param>
private void DrawLabels3D(
ChartArea area,
ChartGraphics graph,
CommonElements common,
RectangleF rectSize,
DataPoint3D pointEx,
Series ser,
double barStartPosition,
double barSize,
double width,
int pointIndex)
{
DataPoint point = pointEx.dataPoint;
//************************************************************
// Draw data point value label
//************************************************************
if (ser.IsValueShownAsLabel || point.IsValueShownAsLabel || point.Label.Length > 0)
{
// Label rectangle
RectangleF rectLabel = RectangleF.Empty;
// Label text format
using (StringFormat format = new StringFormat())
{
//************************************************************
// Get label text
//************************************************************
string text;
if( point.Label.Length == 0 )
{
text = ValueConverter.FormatValue(
ser.Chart,
point,
point.Tag,
GetYValue(common, area, ser, point, pointIndex, 0 ),
point.LabelFormat,
ser.YValueType,
ChartElementType.DataPoint);
}
else
{
text = point.ReplaceKeywords(point.Label);
}
//************************************************************
// Calculate marker size
//************************************************************
SizeF markerSize = SizeF.Empty;
if(point.MarkerStyle != MarkerStyle.None || point.MarkerImage.Length > 0)
{
// Check if this marker should be drawn
if((pointEx.index % ser.MarkerStep) == 0)
{
// Find relative marker size
if (point.MarkerImage.Length == 0)
{
markerSize.Width = point.MarkerSize;
markerSize.Height = point.MarkerSize;
}
else
common.ImageLoader.GetAdjustedImageSize(point.MarkerImage, graph.Graphics, ref markerSize);
markerSize = graph.GetRelativeSize( markerSize );
}
}
//************************************************************
// Check labels style custom properties
//************************************************************
BarValueLabelDrawingStyle drawingStyle = defLabelDrawingStyle;
string valueLabelAttrib = "";
if (point.IsCustomPropertySet(CustomPropertyName.BarLabelStyle))
{
valueLabelAttrib = point[CustomPropertyName.BarLabelStyle];
}
else if (ser.IsCustomPropertySet(CustomPropertyName.BarLabelStyle))
{
valueLabelAttrib = ser[CustomPropertyName.BarLabelStyle];
}
if (valueLabelAttrib != null && valueLabelAttrib.Length > 0)
{
if (String.Compare(valueLabelAttrib, "Left", StringComparison.OrdinalIgnoreCase) == 0)
drawingStyle = BarValueLabelDrawingStyle.Left;
else if (String.Compare(valueLabelAttrib, "Right", StringComparison.OrdinalIgnoreCase) == 0)
drawingStyle = BarValueLabelDrawingStyle.Right;
else if (String.Compare(valueLabelAttrib, "Center", StringComparison.OrdinalIgnoreCase) == 0)
drawingStyle = BarValueLabelDrawingStyle.Center;
else if (String.Compare(valueLabelAttrib, "Outside", StringComparison.OrdinalIgnoreCase) == 0)
drawingStyle = BarValueLabelDrawingStyle.Outside;
}
//************************************************************
// Make sure label fits. Otherwise change it style
//************************************************************
bool labelFit = false;
bool labelSwitched = false;
bool labelSwitchedBack = false;
float prevWidth = 0f;
while (!labelFit)
{
// Label text format
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Center;
// LabelStyle rectangle
if (barStartPosition < barSize)
{
rectLabel.X = rectSize.Right;
rectLabel.Width = area.PlotAreaPosition.Right - rectSize.Right;
}
else
{
rectLabel.X = area.PlotAreaPosition.X;
rectLabel.Width = rectSize.X - area.PlotAreaPosition.X;
}
// Adjust label rectangle
rectLabel.Y = rectSize.Y - (float)width / 2F;
rectLabel.Height = rectSize.Height + (float)width;
// Adjust label position depending on the drawing style
if (drawingStyle == BarValueLabelDrawingStyle.Outside)
{
// Adjust position if point marker is drawn
if (!markerSize.IsEmpty)
{
rectLabel.Width -= (float)Math.Min(rectLabel.Width, markerSize.Width / 2F);
if (barStartPosition < barSize)
{
rectLabel.X += (float)Math.Min(rectLabel.Width, markerSize.Width / 2F);
}
}
}
else if (drawingStyle == BarValueLabelDrawingStyle.Left)
{
rectLabel = rectSize;
format.Alignment = StringAlignment.Near;
}
else if (drawingStyle == BarValueLabelDrawingStyle.Center)
{
rectLabel = rectSize;
format.Alignment = StringAlignment.Center;
}
else if (drawingStyle == BarValueLabelDrawingStyle.Right)
{
rectLabel = rectSize;
format.Alignment = StringAlignment.Far;
// Adjust position if point marker is drawn
if (!markerSize.IsEmpty)
{
rectLabel.Width -= (float)Math.Min(rectLabel.Width, markerSize.Width / 2F);
if (barStartPosition >= barSize)
{
rectLabel.X += (float)Math.Min(rectLabel.Width, markerSize.Width / 2F);
}
}
}
// Reversed string alignment
if (barStartPosition >= barSize)
{
if (format.Alignment == StringAlignment.Far)
format.Alignment = StringAlignment.Near;
else if (format.Alignment == StringAlignment.Near)
format.Alignment = StringAlignment.Far;
}
// Make sure value label fits rectangle.
SizeF valueTextSize = graph.MeasureStringRel(text, point.Font);
if (!labelSwitched &&
!labelSwitchedBack &&
valueTextSize.Width > rectLabel.Width)
{
// Switch label style only once
labelSwitched = true;
prevWidth = rectLabel.Width;
// If text do not fit - try to switch between Outside/Inside drawing styles
if (drawingStyle == BarValueLabelDrawingStyle.Outside)
{
drawingStyle = BarValueLabelDrawingStyle.Right;
}
else
{
drawingStyle = BarValueLabelDrawingStyle.Outside;
}
}
else
{
// If label do not fit either Outside or to the Right,
// select the style that has more space available.
if (labelSwitched &&
!labelSwitchedBack &&
valueTextSize.Width > rectLabel.Width - 1 &&
prevWidth > rectLabel.Width)
{
labelSwitchedBack = true;
// Change back to the previous labels style
if (drawingStyle == BarValueLabelDrawingStyle.Outside)
{
drawingStyle = BarValueLabelDrawingStyle.Right;
}
else
{
drawingStyle = BarValueLabelDrawingStyle.Outside;
}
}
else
{
// Do not try to fit labels any more
labelFit = true;
}
}
}
//************************************************************
// Find text rotation center point
//************************************************************
// Measure string size
SizeF size = graph.MeasureStringRel(text, point.Font, new SizeF(rectLabel.Width, rectLabel.Height), format);
PointF rotationCenter = PointF.Empty;
if (format.Alignment == StringAlignment.Near)
{ // Near
rotationCenter.X = rectLabel.X + size.Width / 2;
}
else if (format.Alignment == StringAlignment.Far)
{ // Far
rotationCenter.X = rectLabel.Right - size.Width / 2;
}
else
{ // Center
rotationCenter.X = (rectLabel.Left + rectLabel.Right) / 2;
}
if (format.LineAlignment == StringAlignment.Near)
{ // Near
rotationCenter.Y = rectLabel.Top + size.Height / 2;
}
else if (format.LineAlignment == StringAlignment.Far)
{ // Far
rotationCenter.Y = rectLabel.Bottom - size.Height / 2;
}
else
{ // Center
rotationCenter.Y = (rectLabel.Bottom + rectLabel.Top) / 2;
}
// Reset string alignment to center point
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
//************************************************************
// Adjust label rotation angle
//************************************************************
int angle = point.LabelAngle;
// Get projection coordinates
Point3D[] rotationCenterProjection = new Point3D[] {
new Point3D(rotationCenter.X, rotationCenter.Y, pointEx.zPosition + pointEx.depth),
new Point3D(rotationCenter.X - 20f, rotationCenter.Y, pointEx.zPosition + pointEx.depth) };
// Transform coordinates of text rotation point
area.matrix3D.TransformPoints(rotationCenterProjection);
// Adjust rotation point
rotationCenter = rotationCenterProjection[0].PointF;
// Adjust angle of the horisontal text
if (angle == 0 || angle == 180)
{
// Convert coordinates to absolute
rotationCenterProjection[0].PointF = graph.GetAbsolutePoint(rotationCenterProjection[0].PointF);
rotationCenterProjection[1].PointF = graph.GetAbsolutePoint(rotationCenterProjection[1].PointF);
// Calcuate axis angle
float angleXAxis = (float)Math.Atan(
(rotationCenterProjection[1].Y - rotationCenterProjection[0].Y) /
(rotationCenterProjection[1].X - rotationCenterProjection[0].X));
angleXAxis = (float)Math.Round(angleXAxis * 180f / (float)Math.PI);
angle += (int)angleXAxis;
}
// Calculate label background position
RectangleF labelBackPosition = RectangleF.Empty;
if (common.ProcessModeRegions ||
!point.LabelBackColor.IsEmpty ||
!point.LabelBorderColor.IsEmpty)
{
SizeF sizeLabel = new SizeF(size.Width, size.Height);
sizeLabel.Height += sizeLabel.Height / 8;
sizeLabel.Width += sizeLabel.Width / text.Length;
labelBackPosition = new RectangleF(
rotationCenter.X - sizeLabel.Width / 2,
rotationCenter.Y - sizeLabel.Height / 2,
sizeLabel.Width,
sizeLabel.Height);
}
//************************************************************
// Draw label
//************************************************************
using (Brush brush = new SolidBrush(point.LabelForeColor))
{
graph.DrawPointLabelStringRel(
common,
text,
point.Font,
brush,
rotationCenter,
format,
angle,
labelBackPosition,
point.LabelBackColor,
point.LabelBorderColor,
point.LabelBorderWidth,
point.LabelBorderDashStyle,
ser,
point,
pointIndex);
}
}
}
}
#endregion
#region SmartLabelStyle methods
/// <summary>
/// Adds markers position to the list. Used to check SmartLabelStyle overlapping.
/// </summary>
/// <param name="common">Common chart elements.</param>
/// <param name="area">Chart area.</param>
/// <param name="series">Series values to be used.</param>
/// <param name="list">List to add to.</param>
public void AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list)
{
// NOTE: Bar chart do not support SmartLabelStyle feature.
}
#endregion
#region IDisposable interface implementation
/// <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 virtual void Dispose(bool disposing)
{
//Nothing to dispose at the base class.
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
#region Points drawing order comparer class
/// <summary>
/// Chart 3D engine relies on the data point drawing order
/// to achieve correct visual appearance. All data points
/// have to be drawn in the correct order depending on the
/// 3D angles, perspective and the depth of the series.
///
/// BarPointsDrawingOrderComparer class is used sort data
/// points of the Bar chart type.
/// </summary>
internal class BarPointsDrawingOrderComparer : IComparer
{
#region Fields
/// <summary>
/// Chart area object reference.
/// </summary>
private ChartArea _area = null;
/// <summary>
/// Area X position where visible sides are switched.
/// </summary>
private Point3D _areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN);
/// <summary>
/// Selection mode. Points order should be reversed.
/// </summary>
private bool _selection = false;
#endregion // Fields
#region Methods
/// <summary>
/// Public constructor.
/// </summary>
/// <param name="area">Chart area.</param>
/// <param name="selection">Selection indicator.</param>
/// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface everlapping.</param>
public BarPointsDrawingOrderComparer(ChartArea area, bool selection, COPCoordinates coord)
{
this._area = area;
this._selection = selection;
// Get center of projection
if(area.DrawPointsToCenter(ref coord))
{
// Get COP
_areaProjectionCenter = area.GetCenterOfProjection(coord);
// Switch X & Y coordinates
float val = _areaProjectionCenter.X;
_areaProjectionCenter.X = _areaProjectionCenter.Y;
_areaProjectionCenter.Y = val;
}
}
/// <summary>
/// Comarer method.
/// </summary>
/// <param name="o1">First object.</param>
/// <param name="o2">Second object.</param>
/// <returns>Comparison result.</returns>
public int Compare(object o1, object o2)
{
DataPoint3D point1 = (DataPoint3D) o1;
DataPoint3D point2 = (DataPoint3D) o2;
int result = 0;
if(point1.xPosition < point2.xPosition)
{
result = -1;
}
else if(point1.xPosition > point2.xPosition)
{
result = 1;
}
else
{
// If X coordinate is the same - filter by Y coordinate
if(point1.yPosition < point2.yPosition)
{
result = 1;
}
else if(point1.yPosition > point2.yPosition)
{
result = -1;
}
// Order points from sides to center
if(!float.IsNaN(_areaProjectionCenter.Y))
{
double yMin1 = Math.Min(point1.yPosition, point1.height);
double yMax1 = Math.Max(point1.yPosition, point1.height);
double yMin2 = Math.Min(point2.yPosition, point2.height);
double yMax2 = Math.Max(point2.yPosition, point2.height);
if(_area.IsBottomSceneWallVisible())
{
if( yMin1 <= _areaProjectionCenter.Y && yMin2 <= _areaProjectionCenter.Y )
{
result *= -1;
}
else if( yMin1 <= _areaProjectionCenter.Y)
{
result = 1;
}
}
else
{
if( yMax1 >= _areaProjectionCenter.Y && yMax2 >= _areaProjectionCenter.Y )
{
result *= 1;
}
else if( yMax1 >= _areaProjectionCenter.Y)
{
result = 1;
}
else
{
result *= -1;
}
}
}
// Reversed order if looking from left or right
else if(!_area.DrawPointsInReverseOrder())
{
result *= -1;
}
}
if(point1.xPosition != point2.xPosition)
{
// Order points from sides to center
if(!float.IsNaN(_areaProjectionCenter.X))
{
if((point1.xPosition + point1.width / 2f) >= _areaProjectionCenter.X &&
(point2.xPosition + point2.width / 2f) >= _areaProjectionCenter.X)
{
result *= -1;
}
}
// Reversed order of points by X value
else if(_area.IsBottomSceneWallVisible())
{
result *= -1;
}
}
return (_selection) ? - result : result;
}
#endregion // Methods
}
#endregion
}
|