|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: StockChart.cs
//
// Namespace: DataVisualization.Charting.ChartTypes
//
// Classes: StockChart, CandleStickChart
//
// Purpose: Stock chart requires 4 Y values High, Low, Open and Close.
//
// The Stock chart displays opening and closing values by using
// markers, which are typically lines or triangles. “OpenCloseStyle”
// custom attribute may be used to control the style of the markers.
// The opening values are shown by the markers on the left, and the
// closing values are shown by the markers on the right.
//
// A stock chart is typically used to illustrate significant stock
// price points including a stock's open, close, high, and low price
// points. However, this type of chart can also be used to analyze
// scientific data, because each series of data displays a high, low,
// open, and close value.
//
// Reviewed: AG - Aug 6, 2002
// AG - Microsoft 7, 2007
//
//===================================================================
#region Used namespaces
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting.Utilities;
#else
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 Open/close marks style enumeration
/// <summary>
/// Style of the Open-Close marks in the stock chart
/// </summary>
internal enum StockOpenCloseMarkStyle
{
/// <summary>
/// Line
/// </summary>
Line,
/// <summary>
/// Triangle
/// </summary>
Triangle,
/// <summary>
/// CandleStick. Color of the bar depends if Open value was bigger than Close value.
/// </summary>
Candlestick
}
#endregion
/// <summary>
/// CandleStick class provides chart unique name and changes the marking
/// style in the StockChart class to StockOpenCloseMarkStyle.CandleStick.
/// </summary>
internal class CandleStickChart : StockChart
{
#region Constructor
/// <summary>
/// CandleStick chart constructor.
/// </summary>
public CandleStickChart() : base(StockOpenCloseMarkStyle.Candlestick)
{
forceCandleStick = true;
}
#endregion
#region IChartType interface implementation
/// <summary>
/// Chart type name
/// </summary>
override public string Name { get{ return ChartTypeNames.Candlestick;}}
/// <summary>
/// Gets chart type image.
/// </summary>
/// <param name="registry">Chart types registry object.</param>
/// <returns>Chart type image.</returns>
override public System.Drawing.Image GetImage(ChartTypeRegistry registry)
{
return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
}
#endregion
}
/// <summary>
/// StockChart class provides 2D/3D drawing and hit testing
/// functionality for the Stock and CandleStick charts.
/// </summary>
internal class StockChart : IChartType
{
#region Fields
/// <summary>
/// Vertical axis
/// </summary>
internal Axis VAxis { get; set; }
/// <summary>
/// Horizontal axis
/// </summary>
internal Axis HAxis { get; set; }
/// <summary>
/// Default open-close style
/// </summary>
protected StockOpenCloseMarkStyle openCloseStyle = StockOpenCloseMarkStyle.Line;
/// <summary>
/// Indicates that only candle-stick type of the open-close marks should be used
/// </summary>
protected bool forceCandleStick = false;
#endregion
#region Constructor
/// <summary>
/// Stock chart constructor.
/// </summary>
public StockChart()
{
}
/// <summary>
/// Stock chart constructor.
/// </summary>
/// <param name="style">Open-close marks default style.</param>
public StockChart(StockOpenCloseMarkStyle style)
{
this.openCloseStyle = style;
}
#endregion
#region IChartType interface implementation
/// <summary>
/// Chart type name
/// </summary>
virtual public string Name { get{ return ChartTypeNames.Stock;}}
/// <summary>
/// True if chart type is stacked
/// </summary>
virtual 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>
virtual 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>
virtual public bool SupportLogarithmicAxes { get{ return true;} }
/// <summary>
/// True if chart type requires to switch the value (Y) axes position
/// </summary>
virtual public bool SwitchValueAxes { get{ return false;} }
/// <summary>
/// True if chart series can be placed side-by-side.
/// </summary>
public bool SideBySideSeries { get{ return false;} }
/// <summary>
/// True if each data point of a chart must be represented in the legend
/// </summary>
virtual public bool DataPointsInLegend { get{ return false;} }
/// <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 false;} }
/// <summary>
/// True if palette colors should be applied for each data paoint.
/// Otherwise the color is applied to the series.
/// </summary>
virtual public bool ApplyPaletteColorsToPoints { 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 true; } }
/// <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>
/// 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>
virtual public LegendImageStyle GetLegendImageStyle(Series series)
{
return LegendImageStyle.Line;
}
/// <summary>
/// Number of supported Y value(s) per point
/// </summary>
virtual public int YValuesPerPoint { get { return 4; } }
/// <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");
}
#endregion
#region Painting and Selection methods
/// <summary>
/// Paint stock 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>
virtual public void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )
{
ProcessChartType( false, graph, common, area, seriesToDraw );
}
/// <summary>
/// This method recalculates size of the bars. This method is used
/// from Paint or Select method.
/// </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>
virtual protected void ProcessChartType(
bool selection,
ChartGraphics graph,
CommonElements common,
ChartArea area,
Series seriesToDraw )
{
// Prosess 3D chart type
if(area.Area3DStyle.Enable3D)
{
ProcessChartType3D( selection, graph, common, area, seriesToDraw );
return;
}
// All data series from chart area which have Stock chart type
List<string> typeSeries = area.GetSeriesFromChartType(this.Name);
// Zero X values mode.
bool indexedSeries = ChartHelper.IndexedSeries(common, typeSeries.ToArray() );
//************************************************************
//** Loop through all series
//************************************************************
foreach( Series ser in common.DataManager.Series )
{
// Process non empty series of the area with stock chart type
if( String.Compare( ser.ChartTypeName, this.Name, StringComparison.OrdinalIgnoreCase ) != 0
|| ser.ChartArea != area.Name || !ser.IsVisible())
{
continue;
}
// Check that we have at least 4 Y values
if(ser.YValuesPerPoint < 4)
{
throw(new ArgumentException(SR.ExceptionChartTypeRequiresYValues("StockChart", "4")));
}
// Set active horizontal/vertical axis
HAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
VAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
// Get interval between points
double interval = (indexedSeries) ? 1 : area.GetPointsInterval( HAxis.IsLogarithmic, HAxis.logarithmBase );
// Calculates the width of the candles.
float width = (float)(ser.GetPointWidth(graph, HAxis, interval, 0.8));
// Call Back Paint event
if( !selection )
{
common.Chart.CallOnPrePaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
}
//************************************************************
//** Series data points loop
//************************************************************
int index = 1;
foreach( DataPoint point in ser.Points )
{
// Reset pre-calculated point position
point.positionRel = new PointF(float.NaN, float.NaN);
// Get point X position
double xValue = point.XValue;
if( indexedSeries )
{
xValue = (double)index;
}
float xPosition = (float)HAxis.GetPosition( xValue );
double yValue0 = VAxis.GetLogValue( point.YValues[0] );
double yValue1 = VAxis.GetLogValue( point.YValues[1] );
xValue = HAxis.GetLogValue(xValue);
// Check if chart is completly out of the data scaleView
if(xValue < HAxis.ViewMinimum ||
xValue > HAxis.ViewMaximum ||
(yValue0 < VAxis.ViewMinimum && yValue1 < VAxis.ViewMinimum) ||
(yValue0 > VAxis.ViewMaximum && yValue1 > VAxis.ViewMaximum) )
{
++index;
continue;
}
// Make sure High/Low values are in data scaleView range
double high = VAxis.GetLogValue( point.YValues[0] );
double low = VAxis.GetLogValue( point.YValues[1] );
if( high > VAxis.ViewMaximum )
{
high = VAxis.ViewMaximum;
}
if( high < VAxis.ViewMinimum )
{
high = VAxis.ViewMinimum;
}
high = (float)VAxis.GetLinearPosition(high);
if( low > VAxis.ViewMaximum )
{
low = VAxis.ViewMaximum;
}
if( low < VAxis.ViewMinimum )
{
low = VAxis.ViewMinimum;
}
low = VAxis.GetLinearPosition(low);
// Remeber pre-calculated point position
point.positionRel = new PointF((float)xPosition, (float)high);
if( common.ProcessModePaint )
{
// Check if chart is partialy in the data scaleView
bool clipRegionSet = false;
if(xValue == HAxis.ViewMinimum || xValue == HAxis.ViewMaximum )
{
// Set clipping region for line drawing
graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
clipRegionSet = true;
}
// Start Svg Selection mode
graph.StartHotRegion( point );
// Draw Hi-Low line
graph.DrawLineRel(
point.Color,
point.BorderWidth,
point.BorderDashStyle,
new PointF(xPosition, (float)high),
new PointF(xPosition, (float)low),
ser.ShadowColor,
ser.ShadowOffset );
// Draw Open-Close marks
DrawOpenCloseMarks(graph, area, ser, point, xPosition, width);
// End Svg Selection mode
graph.EndHotRegion( );
// Reset Clip Region
if(clipRegionSet)
{
graph.ResetClip();
}
}
if( common.ProcessModeRegions )
{
// Calculate rect around the hi-lo line and open-close marks
RectangleF areaRect = RectangleF.Empty;
areaRect.X = xPosition - width / 2f;
areaRect.Y = (float)Math.Min(high, low);
areaRect.Width = width;
areaRect.Height = (float)Math.Max(high, low) - areaRect.Y;
common.HotRegionsList.AddHotRegion(
areaRect,
point,
ser.Name,
index - 1 );
}
++index;
}
//************************************************************
//** Second series data points loop, when markers and labels
//** are drawn.
//************************************************************
int markerIndex = 0;
index = 1;
foreach( DataPoint point in ser.Points )
{
// Get point X position
double xValue = point.XValue;
if( indexedSeries )
{
xValue = (double)index;
}
float xPosition = (float)HAxis.GetPosition( xValue );
double yValue0 = VAxis.GetLogValue( point.YValues[0] );
double yValue1 = VAxis.GetLogValue( point.YValues[1] );
xValue = HAxis.GetLogValue(xValue);
// Check if chart is completly out of the data scaleView
if(xValue < HAxis.ViewMinimum ||
xValue > HAxis.ViewMaximum ||
(yValue0 < VAxis.ViewMinimum && yValue1 < VAxis.ViewMinimum) ||
(yValue0 > VAxis.ViewMaximum && yValue1 > VAxis.ViewMaximum) )
{
++index;
continue;
}
// Make sure High/Low values are in data scaleView range
double high = VAxis.GetLogValue( point.YValues[0] );
double low = VAxis.GetLogValue( point.YValues[1] );
if( high > VAxis.ViewMaximum )
{
high = VAxis.ViewMaximum;
}
if( high < VAxis.ViewMinimum )
{
high = VAxis.ViewMinimum;
}
high = (float)VAxis.GetLinearPosition(high);
if( low > VAxis.ViewMaximum )
{
low = VAxis.ViewMaximum;
}
if( low < VAxis.ViewMinimum )
{
low = VAxis.ViewMinimum;
}
low = VAxis.GetLinearPosition(low);
// Draw marker
if(point.MarkerStyle != MarkerStyle.None || point.MarkerImage.Length > 0)
{
// Get marker size
SizeF markerSize = SizeF.Empty;
markerSize.Width = point.MarkerSize;
markerSize.Height = point.MarkerSize;
if (graph != null && graph.Graphics != null)
{
// Marker size is in pixels and we do the mapping for higher DPIs
markerSize.Width = point.MarkerSize * graph.Graphics.DpiX / 96;
markerSize.Height = point.MarkerSize * graph.Graphics.DpiY / 96;
}
if (point.MarkerImage.Length > 0)
common.ImageLoader.GetAdjustedImageSize(point.MarkerImage, graph.Graphics, ref markerSize);
// Get marker position
PointF markerPosition = PointF.Empty;
markerPosition.X = xPosition;
markerPosition.Y = (float)high - graph.GetRelativeSize(markerSize).Height/2f;
// Draw marker
if(markerIndex == 0)
{
// Draw the marker
graph.DrawMarkerRel(markerPosition,
point.MarkerStyle,
(int)markerSize.Height,
(point.MarkerColor == Color.Empty) ? point.Color : point.MarkerColor,
(point.MarkerBorderColor == Color.Empty) ? point.BorderColor : point.MarkerBorderColor,
point.MarkerBorderWidth,
point.MarkerImage,
point.MarkerImageTransparentColor,
(point.series != null) ? point.series.ShadowOffset : 0,
(point.series != null) ? point.series.ShadowColor : Color.Empty,
new RectangleF(markerPosition.X, markerPosition.Y, markerSize.Width, markerSize.Height));
if( common.ProcessModeRegions )
{
// Get relative marker size
SizeF relativeMarkerSize = graph.GetRelativeSize(markerSize);
// Insert area just after the last custom area
int insertIndex = common.HotRegionsList.FindInsertIndex();
common.HotRegionsList.FindInsertIndex();
// Insert circle area
if(point.MarkerStyle == MarkerStyle.Circle)
{
float[] circCoord = new float[3];
circCoord[0] = markerPosition.X;
circCoord[1] = markerPosition.Y;
circCoord[2] = relativeMarkerSize.Width/2f;
common.HotRegionsList.AddHotRegion(
insertIndex,
graph,
circCoord[0],
circCoord[1],
circCoord[2],
point,
ser.Name,
index - 1 );
}
// All other markers represented as rectangles
else
{
common.HotRegionsList.AddHotRegion(
new RectangleF(markerPosition.X - relativeMarkerSize.Width/2f, markerPosition.Y - relativeMarkerSize.Height/2f, relativeMarkerSize.Width, relativeMarkerSize.Height),
point,
ser.Name,
index - 1 );
}
}
}
// Increase the markers counter
++markerIndex;
if(ser.MarkerStep == markerIndex)
{
markerIndex = 0;
}
}
// Draw label
DrawLabel(common, area, graph, ser, point, new PointF(xPosition, (float)Math.Min(high, low)), index);
// Increase point counter
++index;
}
// Call Paint event
if( !selection )
{
common.Chart.CallOnPostPaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
}
}
}
/// <summary>
/// Draws stock chart open-close marks depending on selected style.
/// </summary>
/// <param name="graph">Chart graphics object.</param>
/// <param name="area">Chart area.</param>
/// <param name="ser">Data point series.</param>
/// <param name="point">Data point to draw.</param>
/// <param name="xPosition">X position.</param>
/// <param name="width">Point width.</param>
virtual protected void DrawOpenCloseMarks(
ChartGraphics graph,
ChartArea area,
Series ser,
DataPoint point,
float xPosition,
float width)
{
double openY = VAxis.GetLogValue( point.YValues[2] );
double closeY = VAxis.GetLogValue( point.YValues[3] );
// Check if mark is inside data scaleView
if( (openY > VAxis.ViewMaximum ||
openY < VAxis.ViewMinimum) &&
(closeY > VAxis.ViewMaximum ||
closeY < VAxis.ViewMinimum) )
{
//return;
}
// Calculate open-close position
float open = (float)VAxis.GetLinearPosition(openY);
float close = (float)VAxis.GetLinearPosition(closeY);
SizeF absSize = graph.GetAbsoluteSize(new SizeF(width, width));
float height = graph.GetRelativeSize(absSize).Height;
// Detect style
StockOpenCloseMarkStyle style = openCloseStyle;
string styleType = "";
if(point.IsCustomPropertySet(CustomPropertyName.OpenCloseStyle))
{
styleType = point[CustomPropertyName.OpenCloseStyle];
}
else if(ser.IsCustomPropertySet(CustomPropertyName.OpenCloseStyle))
{
styleType = ser[CustomPropertyName.OpenCloseStyle];
}
if(styleType != null && styleType.Length > 0)
{
if(String.Compare(styleType, "Candlestick", StringComparison.OrdinalIgnoreCase) == 0)
{
style = StockOpenCloseMarkStyle.Candlestick;
}
else if (String.Compare(styleType, "Triangle", StringComparison.OrdinalIgnoreCase) == 0)
{
style = StockOpenCloseMarkStyle.Triangle;
}
else if (String.Compare(styleType, "Line", StringComparison.OrdinalIgnoreCase) == 0)
{
style = StockOpenCloseMarkStyle.Line;
}
}
// Get attribute which controls if open/close marks are shown
bool showOpen = true;
bool showClose = true;
string showOpenClose = "";
if(point.IsCustomPropertySet(CustomPropertyName.ShowOpenClose))
{
showOpenClose = point[CustomPropertyName.ShowOpenClose];
}
else if(ser.IsCustomPropertySet(CustomPropertyName.ShowOpenClose))
{
showOpenClose = ser[CustomPropertyName.ShowOpenClose];
}
if(showOpenClose != null && showOpenClose.Length > 0)
{
if(String.Compare(showOpenClose, "Both", StringComparison.OrdinalIgnoreCase) == 0)
{
showOpen = true;
showClose = true;
}
else if (String.Compare(showOpenClose, "Open", StringComparison.OrdinalIgnoreCase) == 0)
{
showOpen = true;
showClose = false;
}
else if (String.Compare(showOpenClose, "Close", StringComparison.OrdinalIgnoreCase) == 0)
{
showOpen = false;
showClose = true;
}
}
// Check if chart is partialy in the data scaleView
bool clipRegionSet = false;
if( style == StockOpenCloseMarkStyle.Candlestick || (xPosition - width / 2f) < area.PlotAreaPosition.X || (xPosition + width / 2f) > area.PlotAreaPosition.Right)
{
// Set clipping region for line drawing
graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
clipRegionSet = true;
}
// Draw open-close marks as bar
if(forceCandleStick || style == StockOpenCloseMarkStyle.Candlestick)
{
// Colors used to draw bar of the open-close style
ColorConverter colorConverter = new ColorConverter();
Color priceUpColor = point.Color;
Color priceDownColor = point.BackSecondaryColor;
// Check if special color properties are set
string attrValue = point[CustomPropertyName.PriceUpColor];
if(attrValue != null && attrValue.Length > 0)
{
bool failed = false;
try
{
priceUpColor = (Color)colorConverter.ConvertFromString(attrValue);
}
catch (ArgumentException)
{
failed = true;
}
catch (NotSupportedException)
{
failed = true;
}
if (failed)
{
priceUpColor = (Color)colorConverter.ConvertFromInvariantString(attrValue);
}
}
attrValue = point[CustomPropertyName.PriceDownColor];
if(attrValue != null && attrValue.Length > 0)
{
bool failed = false;
try
{
priceDownColor = (Color)colorConverter.ConvertFromString(attrValue);
}
catch (ArgumentException)
{
failed = true;
}
catch (NotSupportedException)
{
failed = true;
}
if (failed)
{
priceDownColor = (Color)colorConverter.ConvertFromInvariantString(attrValue);
}
}
// Calculate bar rectangle
RectangleF rect = RectangleF.Empty;
rect.Y = (float)Math.Min(open, close);
rect.X = xPosition - width / 2f;
rect.Height = (float)Math.Max(open, close) - rect.Y;
rect.Width = width;
// Bar and border color
Color barColor = (open > close) ? priceUpColor : priceDownColor;
Color barBorderColor = (point.BorderColor == Color.Empty) ? (barColor == Color.Empty) ? point.Color : barColor : point.BorderColor;
// Get absolute height
SizeF sizeOfHeight = new SizeF( rect.Height, rect.Height );
sizeOfHeight = graph.GetAbsoluteSize( sizeOfHeight );
// Draw open-close bar
if( sizeOfHeight.Height > 1 )
{
graph.FillRectangleRel(
rect,
barColor,
point.BackHatchStyle,
point.BackImage,
point.BackImageWrapMode,
point.BackImageTransparentColor,
point.BackImageAlignment,
point.BackGradientStyle,
point.BackSecondaryColor,
barBorderColor,
point.BorderWidth,
point.BorderDashStyle,
ser.ShadowColor,
ser.ShadowOffset,
PenAlignment.Inset );
}
else
{
graph.DrawLineRel(barBorderColor, point.BorderWidth, point.BorderDashStyle,
new PointF(rect.X, rect.Y),
new PointF(rect.Right, rect.Y),
ser.ShadowColor, ser.ShadowOffset );
}
}
// Draw open-close marks as triangals
else if(style == StockOpenCloseMarkStyle.Triangle)
{
using (GraphicsPath path = new GraphicsPath())
{
PointF point1 = graph.GetAbsolutePoint(new PointF(xPosition, open));
PointF point2 = graph.GetAbsolutePoint(new PointF(xPosition - width / 2f, open + height / 2f));
PointF point3 = graph.GetAbsolutePoint(new PointF(xPosition - width / 2f, open - height / 2f));
using (Brush brush = new SolidBrush(point.Color))
{
// Draw Open mark line
if (showOpen)
{
if (openY <= VAxis.ViewMaximum && openY >= VAxis.ViewMinimum)
{
path.AddLine(point2, point1);
path.AddLine(point1, point3);
path.AddLine(point3, point3);
graph.FillPath(brush, path);
}
}
// Draw close mark line
if (showClose)
{
if (closeY <= VAxis.ViewMaximum && closeY >= VAxis.ViewMinimum)
{
path.Reset();
point1 = graph.GetAbsolutePoint(new PointF(xPosition, close));
point2 = graph.GetAbsolutePoint(new PointF(xPosition + width / 2f, close + height / 2f));
point3 = graph.GetAbsolutePoint(new PointF(xPosition + width / 2f, close - height / 2f));
path.AddLine(point2, point1);
path.AddLine(point1, point3);
path.AddLine(point3, point3);
graph.FillPath(brush, path);
}
}
}
}
}
// Draw ope-close marks as lines
else
{
// Draw Open mark line
if(showOpen)
{
if(openY <= VAxis.ViewMaximum && openY >= VAxis.ViewMinimum)
{
graph.DrawLineRel(point.Color, point.BorderWidth, point.BorderDashStyle,
new PointF(xPosition - width/2f, open),
new PointF(xPosition, open),
ser.ShadowColor, ser.ShadowOffset );
}
}
// Draw Close mark line
if(showClose)
{
if(closeY <= VAxis.ViewMaximum && closeY >= VAxis.ViewMinimum)
{
graph.DrawLineRel(point.Color, point.BorderWidth, point.BorderDashStyle,
new PointF(xPosition, close),
new PointF(xPosition + width/2f, close),
ser.ShadowColor, ser.ShadowOffset );
}
}
}
// Reset Clip Region
if(clipRegionSet)
{
graph.ResetClip();
}
}
/// <summary>
/// Draws stock chart data point label.
/// </summary>
/// <param name="common">The Common elements object</param>
/// <param name="area">Chart area for this chart</param>
/// <param name="graph">Chart graphics object.</param>
/// <param name="ser">Data point series.</param>
/// <param name="point">Data point to draw.</param>
/// <param name="position">Label position.</param>
/// <param name="pointIndex">Data point index in the series.</param>
virtual protected void DrawLabel(
CommonElements common,
ChartArea area,
ChartGraphics graph,
Series ser,
DataPoint point,
PointF position,
int pointIndex)
{
if(ser.IsValueShownAsLabel || point.IsValueShownAsLabel || point.Label.Length > 0)
{
// Label text format
using (StringFormat format = new StringFormat())
{
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Center;
if (point.LabelAngle == 0)
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Far;
}
// Get label text
string text;
if (point.Label.Length == 0)
{
// Check what value to show (High, Low, Open, Close)
int valueIndex = 3;
string valueType = "";
if (point.IsCustomPropertySet(CustomPropertyName.LabelValueType))
{
valueType = point[CustomPropertyName.LabelValueType];
}
else if (ser.IsCustomPropertySet(CustomPropertyName.LabelValueType))
{
valueType = ser[CustomPropertyName.LabelValueType];
}
if (String.Compare(valueType, "High", StringComparison.OrdinalIgnoreCase) == 0)
{
valueIndex = 0;
}
else if (String.Compare(valueType, "Low", StringComparison.OrdinalIgnoreCase) == 0)
{
valueIndex = 1;
}
else if (String.Compare(valueType, "Open", StringComparison.OrdinalIgnoreCase) == 0)
{
valueIndex = 2;
}
text = ValueConverter.FormatValue(
ser.Chart,
point,
point.Tag,
point.YValues[valueIndex],
point.LabelFormat,
ser.YValueType,
ChartElementType.DataPoint);
}
else
{
text = point.ReplaceKeywords(point.Label);
}
// Get text angle
int textAngle = point.LabelAngle;
// Check if text contains white space only
if (text.Trim().Length != 0)
{
SizeF sizeFont = SizeF.Empty;
// Check if Smart Labels are enabled
if (ser.SmartLabelStyle.Enabled)
{
// Get marker size
SizeF markerSize = SizeF.Empty;
markerSize.Width = point.MarkerSize;
markerSize.Height = point.MarkerSize;
if (graph != null && graph.Graphics != null)
{
// Marker size is in pixels and we do the mapping for higher DPIs
markerSize.Width = point.MarkerSize * graph.Graphics.DpiX / 96;
markerSize.Height = point.MarkerSize * graph.Graphics.DpiY / 96;
}
if (point.MarkerImage.Length > 0)
common.ImageLoader.GetAdjustedImageSize(point.MarkerImage, graph.Graphics, ref markerSize);
// Get point label style attribute
markerSize = graph.GetRelativeSize(markerSize);
sizeFont = graph.GetRelativeSize(graph.MeasureString(text, point.Font, new SizeF(1000f, 1000f), StringFormat.GenericTypographic));
// Adjust label position using SmartLabelStyle algorithm
position = area.smartLabels.AdjustSmartLabelPosition(
common,
graph,
area,
ser.SmartLabelStyle,
position,
sizeFont,
format,
position,
markerSize,
LabelAlignmentStyles.Top);
// Smart labels always use 0 degrees text angle
textAngle = 0;
}
// Draw label
if (!position.IsEmpty)
{
RectangleF labelBackPosition = RectangleF.Empty;
if (!point.LabelBackColor.IsEmpty ||
point.LabelBorderWidth > 0 ||
!point.LabelBorderColor.IsEmpty)
{
// Get text size
if (sizeFont.IsEmpty)
{
sizeFont = graph.GetRelativeSize(graph.MeasureString(text, point.Font, new SizeF(1000f, 1000f), StringFormat.GenericTypographic));
}
// Adjust label y coordinate
position.Y -= sizeFont.Height / 8;
// Get label background position
SizeF sizeLabel = new SizeF(sizeFont.Width, sizeFont.Height);
sizeLabel.Height += sizeFont.Height / 8;
sizeLabel.Width += sizeLabel.Width / text.Length;
labelBackPosition = PointChart.GetLabelPosition(
graph,
position,
sizeLabel,
format,
true);
}
// Draw label text
using (Brush brush = new SolidBrush(point.LabelForeColor))
{
graph.DrawPointLabelStringRel(
common,
text,
point.Font,
brush,
position,
format,
textAngle,
labelBackPosition,
point.LabelBackColor,
point.LabelBorderColor,
point.LabelBorderWidth,
point.LabelBorderDashStyle,
ser,
point,
pointIndex - 1);
}
}
}
}
}
}
#endregion
#region 3D Drawing and Selection methods
/// <summary>
/// This method recalculates size of the bars. This method is used
/// from Paint or Select method.
/// </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>
virtual protected void ProcessChartType3D(
bool selection,
ChartGraphics graph,
CommonElements common,
ChartArea area,
Series seriesToDraw )
{
// All data series from chart area which have Stock chart type
List<string> typeSeries = area.GetSeriesFromChartType(this.Name);
// Zero X values mode.
bool indexedSeries = ChartHelper.IndexedSeries(common, typeSeries.ToArray() );
//************************************************************
//** Loop through all series
//************************************************************
foreach( Series ser in common.DataManager.Series )
{
// Process non empty series of the area with stock chart type
if( String.Compare( ser.ChartTypeName, this.Name, StringComparison.OrdinalIgnoreCase ) != 0
|| ser.ChartArea != area.Name || !ser.IsVisible())
{
continue;
}
// Check if drawn series is specified
if(seriesToDraw != null && seriesToDraw.Name != ser.Name)
{
continue;
}
// Check that we have at least 4 Y values
if(ser.YValuesPerPoint < 4)
{
throw(new ArgumentException(SR.ExceptionChartTypeRequiresYValues("StockChart", "4" )));
}
// Set active horizontal/vertical axis
HAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
VAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
// Get interval between points
double interval = (indexedSeries) ? 1 : area.GetPointsInterval( HAxis.IsLogarithmic, HAxis.logarithmBase );
// Calculates the width of the candles.
float width = (float)(ser.GetPointWidth(graph, HAxis, interval, 0.8));
// Call Back Paint event
if( !selection )
{
common.Chart.CallOnPrePaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
}
//************************************************************
//** Get series depth and Z position
//************************************************************
float seriesDepth, seriesZPosition;
area.GetSeriesZPositionAndDepth(ser, out seriesDepth, out seriesZPosition);
//************************************************************
//** Series data points loop
//************************************************************
int index = 1;
foreach( DataPoint point in ser.Points )
{
// Reset pre-calculated point position
point.positionRel = new PointF(float.NaN, float.NaN);
// Get point X position
double xValue = point.XValue;
if( indexedSeries )
{
xValue = (double)index;
}
float xPosition = (float)HAxis.GetPosition( xValue );
double yValue0 = VAxis.GetLogValue( point.YValues[0] );
double yValue1 = VAxis.GetLogValue( point.YValues[1] );
xValue = HAxis.GetLogValue(xValue);
// Check if chart is completly out of the data scaleView
if(xValue < HAxis.ViewMinimum ||
xValue > HAxis.ViewMaximum ||
(yValue0 < VAxis.ViewMinimum && yValue1 < VAxis.ViewMinimum) ||
(yValue0 > VAxis.ViewMaximum && yValue1 > VAxis.ViewMaximum) )
{
++index;
continue;
}
// Check if chart is partialy in the data scaleView
bool clipRegionSet = false;
if(xValue == HAxis.ViewMinimum || xValue == HAxis.ViewMaximum )
{
// Set clipping region for line drawing
graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
clipRegionSet = true;
}
// Make sure High/Low values are in data scaleView range
double high = VAxis.GetLogValue( point.YValues[0] );
double low = VAxis.GetLogValue( point.YValues[1] );
if( high > VAxis.ViewMaximum )
{
high = VAxis.ViewMaximum;
}
if( high < VAxis.ViewMinimum )
{
high = VAxis.ViewMinimum;
}
high = (float)VAxis.GetLinearPosition(high);
if( low > VAxis.ViewMaximum )
{
low = VAxis.ViewMaximum;
}
if( low < VAxis.ViewMinimum )
{
low = VAxis.ViewMinimum;
}
low = VAxis.GetLinearPosition(low);
// Remeber pre-calculated point position
point.positionRel = new PointF((float)xPosition, (float)high);
// 3D Transform coordinates
Point3D[] points = new Point3D[2];
points[0] = new Point3D(xPosition, (float)high, seriesZPosition+seriesDepth/2f);
points[1] = new Point3D(xPosition, (float)low, seriesZPosition+seriesDepth/2f);
area.matrix3D.TransformPoints(points);
// Start Svg Selection mode
graph.StartHotRegion( point );
// Draw Hi-Low line
graph.DrawLineRel(
point.Color,
point.BorderWidth,
point.BorderDashStyle,
points[0].PointF,
points[1].PointF,
ser.ShadowColor,
ser.ShadowOffset );
// Draw Open-Close marks
DrawOpenCloseMarks3D(graph, area, ser, point, xPosition, width, seriesZPosition, seriesDepth);
xPosition = points[0].X;
high = points[0].Y;
low = points[1].Y;
// End Svg Selection mode
graph.EndHotRegion( );
// Reset Clip Region
if(clipRegionSet)
{
graph.ResetClip();
}
if( common.ProcessModeRegions )
{
// Calculate rect around the hi-lo line and open-close marks
RectangleF areaRect = RectangleF.Empty;
areaRect.X = xPosition - width / 2f;
areaRect.Y = (float)Math.Min(high, low);
areaRect.Width = width;
areaRect.Height = (float)Math.Max(high, low) - areaRect.Y;
common.HotRegionsList.AddHotRegion(
areaRect,
point,
ser.Name,
index - 1 );
}
++index;
}
//************************************************************
//** Second series data points loop, when markers and labels
//** are drawn.
//************************************************************
int markerIndex = 0;
index = 1;
foreach( DataPoint point in ser.Points )
{
// Get point X position
double xValue = point.XValue;
if( indexedSeries )
{
xValue = (double)index;
}
float xPosition = (float)HAxis.GetPosition( xValue );
double yValue0 = VAxis.GetLogValue( point.YValues[0] );
double yValue1 = VAxis.GetLogValue( point.YValues[1] );
xValue = HAxis.GetLogValue(xValue);
// Check if chart is completly out of the data scaleView
if(xValue < HAxis.ViewMinimum ||
xValue > HAxis.ViewMaximum ||
(yValue0 < VAxis.ViewMinimum && yValue1 < VAxis.ViewMinimum) ||
(yValue0 > VAxis.ViewMaximum && yValue1 > VAxis.ViewMaximum) )
{
++index;
continue;
}
// Make sure High/Low values are in data scaleView range
double high = VAxis.GetLogValue( point.YValues[0] );
double low = VAxis.GetLogValue( point.YValues[1] );
if( high > VAxis.ViewMaximum )
{
high = VAxis.ViewMaximum;
}
if( high < VAxis.ViewMinimum )
{
high = VAxis.ViewMinimum;
}
high = (float)VAxis.GetLinearPosition(high);
if( low > VAxis.ViewMaximum )
{
low = VAxis.ViewMaximum;
}
if( low < VAxis.ViewMinimum )
{
low = VAxis.ViewMinimum;
}
low = VAxis.GetLinearPosition(low);
// 3D Transform coordinates
Point3D[] points = new Point3D[2];
points[0] = new Point3D(xPosition, (float)high, seriesZPosition+seriesDepth/2f);
points[1] = new Point3D(xPosition, (float)low, seriesZPosition+seriesDepth/2f);
area.matrix3D.TransformPoints(points);
xPosition = points[0].X;
high = points[0].Y;
low = points[1].Y;
// Draw label
DrawLabel(common, area, graph, ser, point, new PointF(xPosition, (float)Math.Min(high, low)), index);
// Draw marker
if(point.MarkerStyle != MarkerStyle.None || point.MarkerImage.Length > 0)
{
// Get marker size
SizeF markerSize = SizeF.Empty;
markerSize.Width = point.MarkerSize;
markerSize.Height = point.MarkerSize;
if (graph != null && graph.Graphics != null)
{
// Marker size is in pixels and we do the mapping for higher DPIs
markerSize.Width = point.MarkerSize * graph.Graphics.DpiX / 96;
markerSize.Height = point.MarkerSize * graph.Graphics.DpiY / 96;
}
if (point.MarkerImage.Length > 0)
common.ImageLoader.GetAdjustedImageSize(point.MarkerImage, graph.Graphics, ref markerSize);
// Get marker position
PointF markerPosition = PointF.Empty;
markerPosition.X = xPosition;
markerPosition.Y = (float)high - graph.GetRelativeSize(markerSize).Height/2f;
// Draw marker
if(markerIndex == 0)
{
// Draw the marker
graph.DrawMarkerRel(markerPosition,
point.MarkerStyle,
(int)markerSize.Height,
(point.MarkerColor == Color.Empty) ? point.Color : point.MarkerColor,
(point.MarkerBorderColor == Color.Empty) ? point.BorderColor : point.MarkerBorderColor,
point.MarkerBorderWidth,
point.MarkerImage,
point.MarkerImageTransparentColor,
(point.series != null) ? point.series.ShadowOffset : 0,
(point.series != null) ? point.series.ShadowColor : Color.Empty,
new RectangleF(markerPosition.X, markerPosition.Y, markerSize.Width, markerSize.Height));
if( common.ProcessModeRegions )
{
// Get relative marker size
SizeF relativeMarkerSize = graph.GetRelativeSize(markerSize);
// Insert area just after the last custom area
int insertIndex = common.HotRegionsList.FindInsertIndex();
common.HotRegionsList.FindInsertIndex();
// Insert circle area
if(point.MarkerStyle == MarkerStyle.Circle)
{
float[] circCoord = new float[3];
circCoord[0] = markerPosition.X;
circCoord[1] = markerPosition.Y;
circCoord[2] = relativeMarkerSize.Width/2f;
common.HotRegionsList.AddHotRegion(
insertIndex,
graph,
circCoord[0],
circCoord[1],
circCoord[2],
point,
ser.Name,
index - 1 );
}
// All other markers represented as rectangles
else
{
common.HotRegionsList.AddHotRegion(
new RectangleF(markerPosition.X - relativeMarkerSize.Width/2f, markerPosition.Y - relativeMarkerSize.Height/2f, relativeMarkerSize.Width, relativeMarkerSize.Height),
point,
ser.Name,
index - 1 );
}
}
}
// Increase the markers counter
++markerIndex;
if(ser.MarkerStep == markerIndex)
{
markerIndex = 0;
}
}
++index;
}
// Call Paint event
if( !selection )
{
common.Chart.CallOnPostPaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
}
}
}
/// <summary>
/// Draws stock chart open-close marks depending on selected style.
/// </summary>
/// <param name="graph">Chart graphics object.</param>
/// <param name="area">Chart area.</param>
/// <param name="ser">Data point series.</param>
/// <param name="point">Data point to draw.</param>
/// <param name="xPosition">X position.</param>
/// <param name="width">Point width.</param>
/// <param name="zPosition">Series Z position.</param>
/// <param name="depth">Series depth.</param>
virtual protected void DrawOpenCloseMarks3D(
ChartGraphics graph,
ChartArea area,
Series ser,
DataPoint point,
float xPosition,
float width,
float zPosition,
float depth)
{
double openY = VAxis.GetLogValue( point.YValues[2] );
double closeY = VAxis.GetLogValue( point.YValues[3] );
// Check if mark is inside data scaleView
if( (openY > VAxis.ViewMaximum ||
openY < VAxis.ViewMinimum) &&
(closeY > VAxis.ViewMaximum ||
closeY < VAxis.ViewMinimum) )
{
//return;
}
// Calculate open-close position
float open = (float)VAxis.GetLinearPosition(openY);
float close = (float)VAxis.GetLinearPosition(closeY);
SizeF absSize = graph.GetAbsoluteSize(new SizeF(width, width));
float height = graph.GetRelativeSize(absSize).Height;
// Detect style
StockOpenCloseMarkStyle style = openCloseStyle;
string styleType = "";
if(point.IsCustomPropertySet(CustomPropertyName.OpenCloseStyle))
{
styleType = point[CustomPropertyName.OpenCloseStyle];
}
else if(ser.IsCustomPropertySet(CustomPropertyName.OpenCloseStyle))
{
styleType = ser[CustomPropertyName.OpenCloseStyle];
}
if(styleType != null && styleType.Length > 0)
{
if(String.Compare(styleType, "Candlestick", StringComparison.OrdinalIgnoreCase) == 0)
{
style = StockOpenCloseMarkStyle.Candlestick;
}
else if (String.Compare(styleType, "Triangle", StringComparison.OrdinalIgnoreCase) == 0)
{
style = StockOpenCloseMarkStyle.Triangle;
}
else if (String.Compare(styleType, "Line", StringComparison.OrdinalIgnoreCase) == 0)
{
style = StockOpenCloseMarkStyle.Line;
}
}
// Get attribute which controls if open/close marks are shown
bool showOpen = true;
bool showClose = true;
string showOpenClose = "";
if(point.IsCustomPropertySet(CustomPropertyName.ShowOpenClose))
{
showOpenClose = point[CustomPropertyName.ShowOpenClose];
}
else if(ser.IsCustomPropertySet(CustomPropertyName.ShowOpenClose))
{
showOpenClose = ser[CustomPropertyName.ShowOpenClose];
}
if(showOpenClose != null && showOpenClose.Length > 0)
{
if(String.Compare(showOpenClose, "Both", StringComparison.OrdinalIgnoreCase) == 0)
{
showOpen = true;
showClose = true;
}
else if (String.Compare(showOpenClose, "Open", StringComparison.OrdinalIgnoreCase) == 0)
{
showOpen = true;
showClose = false;
}
else if (String.Compare(showOpenClose, "Close", StringComparison.OrdinalIgnoreCase) == 0)
{
showOpen = false;
showClose = true;
}
}
// Check if chart is partialy in the data scaleView
bool clipRegionSet = false;
if((xPosition - width / 2f) < area.PlotAreaPosition.X || (xPosition + width / 2f) > area.PlotAreaPosition.Right)
{
// Set clipping region for line drawing
graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
clipRegionSet = true;
}
// Draw open-close marks as bar
if(forceCandleStick || style == StockOpenCloseMarkStyle.Candlestick)
{
// Colors used to draw bar of the open-close style
ColorConverter colorConverter = new ColorConverter();
Color priceUpColor = point.Color;
Color priceDownColor = point.BackSecondaryColor;
// Check if special color properties are set
string attrValue = point[CustomPropertyName.PriceUpColor];
if(attrValue != null && attrValue.Length > 0)
{
bool failed = false;
try
{
priceUpColor = (Color)colorConverter.ConvertFromString(attrValue);
}
catch (NotSupportedException)
{
failed = true;
}
catch (ArgumentException)
{
failed = true;
}
if (failed)
{
priceUpColor = (Color)colorConverter.ConvertFromInvariantString(attrValue);
}
}
attrValue = point[CustomPropertyName.PriceDownColor];
if(attrValue != null && attrValue.Length > 0)
{
bool failed = false;
try
{
priceDownColor = (Color)colorConverter.ConvertFromString(attrValue);
}
catch (ArgumentException)
{
failed = true;
}
catch (NotSupportedException)
{
failed = true;
}
if (failed)
{
priceDownColor = (Color)colorConverter.ConvertFromInvariantString(attrValue);
}
}
// Calculate bar rectangle
RectangleF rect = RectangleF.Empty;
rect.Y = (float)Math.Min(open, close);
rect.X = xPosition - width / 2f;
rect.Height = (float)Math.Max(open, close) - rect.Y;
rect.Width = width;
// Bar and border color
Color barColor = (open > close) ? priceUpColor : priceDownColor;
Color barBorderColor = (point.BorderColor == Color.Empty) ? (barColor == Color.Empty) ? point.Color : barColor : point.BorderColor;
// Translate coordinates
Point3D[] points = new Point3D[2];
points[0] = new Point3D(rect.X, rect.Y, zPosition + depth/2f);
points[1] = new Point3D(rect.Right, rect.Bottom, zPosition + depth/2f);
area.matrix3D.TransformPoints(points);
rect.Location = points[0].PointF;
rect.Width = (float)Math.Abs(points[1].X - points[0].X);
rect.Height = (float)Math.Abs(points[1].Y - points[0].Y);
// Draw open-close bar
if(rect.Height > 1)
{
graph.FillRectangleRel(
rect,
barColor,
point.BackHatchStyle,
point.BackImage,
point.BackImageWrapMode,
point.BackImageTransparentColor,
point.BackImageAlignment,
point.BackGradientStyle,
point.BackSecondaryColor,
barBorderColor,
point.BorderWidth,
point.BorderDashStyle,
ser.ShadowColor,
ser.ShadowOffset,
PenAlignment.Inset);
}
else
{
graph.DrawLineRel(barBorderColor, point.BorderWidth, point.BorderDashStyle,
new PointF(rect.X, rect.Y),
new PointF(rect.Right, rect.Y),
ser.ShadowColor, ser.ShadowOffset );
}
}
// Draw open-close marks as triangals
else if(style == StockOpenCloseMarkStyle.Triangle)
{
using (GraphicsPath path = new GraphicsPath())
{
// Translate coordinates
Point3D[] points = new Point3D[3];
points[0] = new Point3D(xPosition, open, zPosition + depth / 2f);
points[1] = new Point3D(xPosition - width / 2f, open + height / 2f, zPosition + depth / 2f);
points[2] = new Point3D(xPosition - width / 2f, open - height / 2f, zPosition + depth / 2f);
area.matrix3D.TransformPoints(points);
points[0].PointF = graph.GetAbsolutePoint(points[0].PointF);
points[1].PointF = graph.GetAbsolutePoint(points[1].PointF);
points[2].PointF = graph.GetAbsolutePoint(points[2].PointF);
using (Brush brush = new SolidBrush(point.Color))
{
// Draw Open mark line
if (showOpen)
{
if (openY <= VAxis.ViewMaximum && openY >= VAxis.ViewMinimum)
{
path.AddLine(points[1].PointF, points[0].PointF);
path.AddLine(points[0].PointF, points[2].PointF);
path.AddLine(points[2].PointF, points[2].PointF);
graph.FillPath(brush, path);
}
}
// Draw close mark line
if (showClose)
{
if (closeY <= VAxis.ViewMaximum && closeY >= VAxis.ViewMinimum)
{
points[0] = new Point3D(xPosition, close, zPosition + depth / 2f);
points[1] = new Point3D(xPosition + width / 2f, close + height / 2f, zPosition + depth / 2f);
points[2] = new Point3D(xPosition + width / 2f, close - height / 2f, zPosition + depth / 2f);
area.matrix3D.TransformPoints(points);
points[0].PointF = graph.GetAbsolutePoint(points[0].PointF);
points[1].PointF = graph.GetAbsolutePoint(points[1].PointF);
points[2].PointF = graph.GetAbsolutePoint(points[2].PointF);
path.Reset();
path.AddLine(points[1].PointF, points[0].PointF);
path.AddLine(points[0].PointF, points[2].PointF);
path.AddLine(points[2].PointF, points[2].PointF);
graph.FillPath(brush, path);
}
}
}
}
}
// Draw ope-close marks as lines
else
{
// Draw Open mark line
if(showOpen)
{
if(openY <= VAxis.ViewMaximum && openY >= VAxis.ViewMinimum)
{
// Translate coordinates
Point3D[] points = new Point3D[2];
points[0] = new Point3D(xPosition - width/2f, open, zPosition + depth/2f);
points[1] = new Point3D(xPosition, open, zPosition + depth/2f);
area.matrix3D.TransformPoints(points);
graph.DrawLineRel(point.Color, point.BorderWidth, point.BorderDashStyle,
points[0].PointF,
points[1].PointF,
ser.ShadowColor, ser.ShadowOffset );
}
}
// Draw Close mark line
if(showClose)
{
if(closeY <= VAxis.ViewMaximum && closeY >= VAxis.ViewMinimum)
{
// Translate coordinates
Point3D[] points = new Point3D[2];
points[0] = new Point3D(xPosition, close, zPosition + depth/2f);
points[1] = new Point3D(xPosition + width/2f, close, zPosition + depth/2f);
area.matrix3D.TransformPoints(points);
graph.DrawLineRel(point.Color, point.BorderWidth, point.BorderDashStyle,
points[0].PointF,
points[1].PointF,
ser.ShadowColor, ser.ShadowOffset );
}
}
}
// Reset Clip Region
if(clipRegionSet)
{
graph.ResetClip();
}
}
#endregion
#region Y values related 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)
{
return point.YValues[yValueIndex];
}
#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)
{
// Check if series is indexed
bool indexedSeries = ChartHelper.IndexedSeries(common, area.GetSeriesFromChartType(this.Name).ToArray() );
//************************************************************
//** Set active horizontal/vertical axis
//************************************************************
Axis hAxis = area.GetAxis(AxisName.X, series.XAxisType, series.XSubAxisName);
Axis vAxis = area.GetAxis(AxisName.Y, series.YAxisType, series.YSubAxisName);
//************************************************************
//** Loop through all data points in the series
//************************************************************
int markerIndex = 0; // Marker index
int index = 1; // Data points loop
foreach( DataPoint point in series.Points )
{
//************************************************************
//** Check if point values are in the chart area
//************************************************************
// Check for min/max Y values
double yValue = GetYValue(common, area, series, point, index - 1, 0);
// Axis is Logarithmic
yValue = vAxis.GetLogValue( yValue );
if( yValue > vAxis.ViewMaximum || yValue < vAxis.ViewMinimum)
{
index++;
continue;
}
// Check for min/max X values
double xValue = (indexedSeries) ? (double)index : point.XValue;
xValue = hAxis.GetLogValue(xValue);
if(xValue > hAxis.ViewMaximum || xValue < hAxis.ViewMinimum)
{
index++;
continue;
}
//************************************************************
//** Get marker position and size
//************************************************************
// Get marker position
PointF markerPosition = PointF.Empty;
markerPosition.Y = (float)vAxis.GetLinearPosition(yValue);
if( indexedSeries )
{
// The formula for position is based on a distance
// from the grid line or nPoints position.
markerPosition.X = (float)hAxis.GetPosition( (double)index );
}
else
{
markerPosition.X = (float)hAxis.GetPosition( point.XValue );
}
// Get point some point properties and save them in variables
string pointMarkerImage = point.MarkerImage;
MarkerStyle pointMarkerStyle = point.MarkerStyle;
// Get marker size
SizeF markerSize = SizeF.Empty;
markerSize.Width = point.MarkerSize;
markerSize.Height = point.MarkerSize;
if (common != null && common.graph != null && common.graph.Graphics != null)
{
// Marker size is in pixels and we do the mapping for higher DPIs
markerSize.Width = point.MarkerSize * common.graph.Graphics.DpiX / 96;
markerSize.Height = point.MarkerSize * common.graph.Graphics.DpiY / 96;
}
if (point.MarkerImage.Length > 0)
if(common.graph != null)
common.ImageLoader.GetAdjustedImageSize(point.MarkerImage, common.graph.Graphics, ref markerSize);
// Transform marker position in 3D space
if(area.Area3DStyle.Enable3D)
{
// Get series depth and Z position
float seriesDepth, seriesZPosition;
area.GetSeriesZPositionAndDepth(series, out seriesDepth, out seriesZPosition);
Point3D[] marker3DPosition = new Point3D[1];
marker3DPosition[0] = new Point3D(
markerPosition.X,
markerPosition.Y,
(float)(seriesZPosition + seriesDepth/2f));
// Transform coordinates
area.matrix3D.TransformPoints(marker3DPosition);
markerPosition = marker3DPosition[0].PointF;
}
// Check if marker visible
if(pointMarkerStyle != MarkerStyle.None ||
pointMarkerImage.Length > 0)
{
// Check marker index
if(markerIndex == 0)
{
markerSize = common.graph.GetRelativeSize(markerSize);
// Add marker position into the list
RectangleF markerRect = new RectangleF(
markerPosition.X - markerSize.Width / 2f,
markerPosition.Y - markerSize.Height,
markerSize.Width,
markerSize.Height);
list.Add(markerRect);
}
// Increase the markers counter
++markerIndex;
if(series.MarkerStep == markerIndex)
{
markerIndex = 0;
}
}
++index;
}
}
#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
}
}
|