/* Copyright (C) 2008 Webyog Softworks Private Limited This file is a part of Visifire Charts. Visifire is a free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. You should have received a copy of the GNU General Public License along with Visifire Charts. If not, see . If GPL is not suitable for your products or company, Webyog provides Visifire under a flexible commercial license designed to meet your specific usage and distribution requirements. If you have already obtained a commercial license from Webyog, you can use this file under those license terms. */ #if WPF using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Animation; #else using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Collections.Generic; #endif using Visifire.Commons; using System.Windows.Shapes; namespace Visifire.Charts { /// /// Visifire.Charts.CandleStick class /// internal class CandleStick { #region Public Methods #endregion #region Public Properties #endregion #region Public Events And Delegates #endregion #region Protected Methods #endregion #region Internal Properties #endregion #region Private Properties #endregion #region Private Delegates #endregion #region Private Methods #endregion #region Internal Methods /// /// Calculate DataPoint width /// /// PlotCanvas width /// PlotCanvas height /// Chart reference /// DataPointWidth as Double internal static Double CalculateDataPointWidth(Double width, Double height, Chart chart) { Double dataPointWidth; Double minDiffValue = chart.PlotDetails.GetMinOfMinDifferencesForXValue(RenderAs.Column, RenderAs.StackedColumn, RenderAs.StackedColumn100, RenderAs.Stock, RenderAs.CandleStick); if (double.IsPositiveInfinity(minDiffValue)) minDiffValue = 0; if (Double.IsNaN(chart.DataPointWidth) || chart.DataPointWidth < 0) { if (minDiffValue != 0) { dataPointWidth = Graphics.ValueToPixelPosition(0, width, (Double)chart.AxesX[0].InternalAxisMinimum, (Double)chart.AxesX[0].InternalAxisMaximum, minDiffValue + (Double)chart.AxesX[0].InternalAxisMinimum); dataPointWidth *= .9; if (dataPointWidth < 5) dataPointWidth = 5; } else { dataPointWidth = width * .3; } } else { dataPointWidth = chart.PlotArea.Width / 100 * chart.DataPointWidth; } if (dataPointWidth < 2) dataPointWidth = 2; return dataPointWidth; } /// /// Get StrokeThickness /// /// /// internal static Double GetStrokeThickness(DataPoint dataPoint) { return dataPoint.InternalBorderThickness.Left == 0 ? ((Double)dataPoint.Parent.LineThickness >= _dataPointWidth / 2 ? _dataPointWidth / 4 : (Double)dataPoint.Parent.LineThickness) : dataPoint.InternalBorderThickness.Left; } /// /// Set DataPoint Values /// /// /// /// /// /// internal static void SetDataPointValues(DataPoint dataPoint, ref Double highY, ref Double lowY, ref Double openY, ref Double closeY) { highY = lowY = openY = closeY = 0; if (dataPoint.YValues.Length >= 4) { openY = dataPoint.YValues[0]; closeY = dataPoint.YValues[1]; highY = dataPoint.YValues[2]; lowY = dataPoint.YValues[3]; } else if (dataPoint.YValues.Length >= 3) { openY = dataPoint.YValues[0]; closeY = dataPoint.YValues[1]; highY = dataPoint.YValues[2]; } else if (dataPoint.YValues.Length >= 2) { openY = dataPoint.YValues[0]; closeY = dataPoint.YValues[1]; } else if (dataPoint.YValues.Length >= 1) { openY = dataPoint.YValues[0]; } } private static Brush GetOpenCloseRectangleBorderbrush(DataPoint dataPoint, Brush dataPointColor) { return (dataPoint.BorderColor == null) ? ((dataPointColor == null) ? dataPoint.Parent.PriceUpColor : dataPointColor) : dataPoint.BorderColor; } private static Brush GetOpenCloseRectangleFillbrush(DataPoint dataPoint, Brush dataPointColor) { if (dataPoint._isPriceUp) return (dataPointColor == null) ? dataPoint.Parent.PriceUpColor : dataPointColor; else return dataPoint.Parent.PriceDownColor; } /// /// Get visual object for CandleStick chart /// /// Width of the chart /// Height of the chart /// plotDetails /// List of DataSeries /// Chart /// Plank depth /// Whether animation is enabled /// CandleStick chart canvas internal static Canvas GetVisualObjectForCandleStick(Double width, Double height, PlotDetails plotDetails, List seriesList, Chart chart, Double plankDepth, bool animationEnabled) { if (Double.IsNaN(width) || Double.IsNaN(height) || width <= 0 || height <= 0) return null; Canvas visual = new Canvas() { Width = width, Height = height }; Canvas labelCanvas = new Canvas() { Width = width, Height = height }; Double depth3d = plankDepth / (plotDetails.Layer3DCount == 0 ? 1 : plotDetails.Layer3DCount) * (chart.View3D ? 1 : 0); Double visualOffset = depth3d * (plotDetails.SeriesDrawingIndex[seriesList[0]] + 1 - (plotDetails.Layer3DCount == 0 ? 0 : 1)); visual.SetValue(Canvas.TopProperty, visualOffset); visual.SetValue(Canvas.LeftProperty, -visualOffset); Double xPositionOfDataPoint, highY = 0, lowY = 0, openY = 0, closeY = 0; Double animationBeginTime = 0; DataSeries _tempDataSeries = null; // Calculate width of a DataPoint _dataPointWidth = CalculateDataPointWidth(width, height, chart); foreach (DataSeries series in seriesList) { if (series.Enabled == false) continue; Canvas seriesCanvas = new Canvas(); PlotGroup plotGroup = series.PlotGroup; _tempDataSeries = series; foreach (DataPoint dataPoint in series.InternalDataPoints) { if (dataPoint.YValues == null || dataPoint.Enabled == false) continue; Brush dataPointColor = (Brush)dataPoint.GetValue(DataPoint.ColorProperty); // Initialize DataPoint faces dataPoint.Faces = new Faces(); dataPoint.Faces.Parts = new List(); SetDataPointValues(dataPoint, ref highY, ref lowY, ref openY, ref closeY); // Calculate required pixel positions xPositionOfDataPoint = Graphics.ValueToPixelPosition(0, width, (Double)plotGroup.AxisX.InternalAxisMinimum, (Double)plotGroup.AxisX.InternalAxisMaximum, dataPoint.InternalXValue); openY = Graphics.ValueToPixelPosition(height, 0, (Double)plotGroup.AxisY.InternalAxisMinimum, (Double)plotGroup.AxisY.InternalAxisMaximum, openY); closeY = Graphics.ValueToPixelPosition(height, 0, (Double)plotGroup.AxisY.InternalAxisMinimum, (Double)plotGroup.AxisY.InternalAxisMaximum, closeY); highY = Graphics.ValueToPixelPosition(height, 0, (Double)plotGroup.AxisY.InternalAxisMinimum, (Double)plotGroup.AxisY.InternalAxisMaximum, highY); lowY = Graphics.ValueToPixelPosition(height, 0, (Double)plotGroup.AxisY.InternalAxisMinimum, (Double)plotGroup.AxisY.InternalAxisMaximum, lowY); dataPoint._isPriceUp = (openY > closeY) ? true : false; // Create DataPoint Visual Canvas dataPointVisual = new Canvas() { //Opacity = 0.5, //Background = new SolidColorBrush(Colors.Yellow), Width = _dataPointWidth, Height = Math.Abs(lowY - highY) }; dataPointVisual.SetValue(Canvas.TopProperty, (highY < lowY) ? highY : lowY); dataPointVisual.SetValue(Canvas.LeftProperty, xPositionOfDataPoint - _dataPointWidth / 2); // Create High and Low Line Line highLowLine = new Line() { X1 = dataPointVisual.Width / 2, X2 = dataPointVisual.Width / 2, Y1 = 0, Y2 = dataPointVisual.Height, Tag = new ElementData() { Element = dataPoint, VisualElementName = "HlLine" }, StrokeThickness = GetStrokeThickness(dataPoint), StrokeDashArray = Graphics.LineStyleToStrokeDashArray(dataPoint.BorderStyle.ToString()) }; if ((Boolean)dataPoint.ShadowEnabled) { Line highLowShadowLine = new Line() { IsHitTestVisible = false, X1 = dataPointVisual.Width / 2 + _shadowDepth, X2 = dataPointVisual.Width / 2 + _shadowDepth, Y1 = 0, Y2 = Math.Max(dataPointVisual.Height -_shadowDepth, 1), Stroke = _shadowColor, StrokeThickness = GetStrokeThickness(dataPoint), StrokeDashArray = Graphics.LineStyleToStrokeDashArray(dataPoint.BorderStyle.ToString()) }; highLowShadowLine.SetValue(Canvas.TopProperty, _shadowDepth); dataPointVisual.Children.Add(highLowShadowLine); } dataPoint.Faces.Parts.Add(highLowLine); dataPoint.Faces.VisualComponents.Add(highLowLine); dataPointVisual.Children.Add(highLowLine); /* Create Open-Close Rectangle * Math.Max is used to make sure that the rectangle is visible * even when the difference between high and low is 0 */ Rectangle openCloseRect = new Rectangle() { Width = _dataPointWidth, Height = Math.Max(Math.Abs(openY - closeY), 1), Stroke = GetOpenCloseRectangleBorderbrush(dataPoint, dataPointColor), StrokeThickness = dataPoint.InternalBorderThickness.Left, StrokeDashArray = Graphics.LineStyleToStrokeDashArray(dataPoint.BorderStyle.ToString()), Tag = new ElementData() { Element = dataPoint, VisualElementName = "OcRect" } }; openCloseRect.SetValue(Canvas.TopProperty, ((closeY > openY) ? openY : closeY) - ((Double)dataPointVisual.GetValue(Canvas.TopProperty))); openCloseRect.SetValue(Canvas.LeftProperty, (Double)0); // If Closing Value is higher than Opening value, then fill //openCloseRect.Fill = (openY > closeY) ? // (dataPointColor ?? dataPoint.Parent.PriceUpColor) : // (dataPointColor ?? dataPoint.Parent.PriceDownColor); openCloseRect.Fill = GetOpenCloseRectangleFillbrush(dataPoint, dataPointColor); if ((Boolean)dataPoint.ShadowEnabled) { Rectangle openCloseShadowRect = new Rectangle() { IsHitTestVisible = false, Fill = _shadowColor, Width = _dataPointWidth, Height = Math.Max(Math.Abs(openY - closeY), 1), Stroke = _shadowColor, StrokeThickness = dataPoint.InternalBorderThickness.Left, StrokeDashArray = Graphics.LineStyleToStrokeDashArray(dataPoint.BorderStyle.ToString()) }; openCloseShadowRect.SetValue(Canvas.TopProperty, (Double)openCloseRect.GetValue(Canvas.TopProperty) + _shadowDepth); openCloseShadowRect.SetValue(Canvas.LeftProperty, (Double)openCloseRect.GetValue(Canvas.LeftProperty) + _shadowDepth); openCloseShadowRect.SetValue(Canvas.ZIndexProperty, -4); dataPointVisual.Children.Add(openCloseShadowRect); } dataPoint.Faces.Parts.Add(openCloseRect); dataPoint.Faces.VisualComponents.Add(openCloseRect); dataPoint.Faces.BorderElements.Add(openCloseRect); dataPointVisual.Children.Add(openCloseRect); // Create Bevel if (dataPoint.Parent.Bevel && _dataPointWidth > 8 && openCloseRect.Height > 6) { Double reduceSize = openCloseRect.StrokeThickness; if (dataPoint.Parent.SelectionEnabled && dataPoint.InternalBorderThickness.Left == 0) reduceSize = 1.5 + reduceSize; if (openCloseRect.Width - 2 * reduceSize >= 0 && openCloseRect.Height - 2 * reduceSize >= 0) { Canvas bevelCanvas = ExtendedGraphics.Get2DRectangleBevel(dataPoint, openCloseRect.Width - 2 * reduceSize, openCloseRect.Height - 2 * reduceSize, 5, 5, Graphics.GetBevelTopBrush(openCloseRect.Fill), Graphics.GetBevelSideBrush(((Boolean)dataPoint.LightingEnabled ? -70 : 0), openCloseRect.Fill), Graphics.GetBevelSideBrush(((Boolean)dataPoint.LightingEnabled ? -110 : 180), openCloseRect.Fill), Graphics.GetBevelSideBrush(90, openCloseRect.Fill)); bevelCanvas.IsHitTestVisible = false; bevelCanvas.SetValue(Canvas.TopProperty, (Double)openCloseRect.GetValue(Canvas.TopProperty) + reduceSize); bevelCanvas.SetValue(Canvas.LeftProperty, reduceSize); // Adding parts of bevel foreach(FrameworkElement fe in bevelCanvas.Children) dataPoint.Faces.Parts.Add(fe); dataPoint.Faces.VisualComponents.Add(bevelCanvas); dataPointVisual.Children.Add(bevelCanvas); } } Brush highLowLineColor; if (dataPointColor == null) highLowLineColor = dataPoint.Parent.PriceUpColor; else highLowLineColor = dataPointColor; if ((Boolean)dataPoint.LightingEnabled) { openCloseRect.Fill = Graphics.GetLightingEnabledBrush(openCloseRect.Fill, "Linear", null); highLowLine.Stroke = Graphics.GetLightingEnabledBrush(highLowLineColor, "Linear", null); } else highLowLine.Stroke = highLowLineColor; seriesCanvas.Children.Add(dataPointVisual); dataPoint.Faces.Visual = dataPointVisual; PlaceLabel(visual, labelCanvas, dataPoint); } // DataPoint loop End // Apply animation to series if (animationEnabled) { if (_tempDataSeries.Storyboard == null) _tempDataSeries.Storyboard = new Storyboard(); _tempDataSeries.Storyboard = AnimationHelper.ApplyOpacityAnimation(seriesCanvas, _tempDataSeries, _tempDataSeries.Storyboard, animationBeginTime, 1, 1); animationBeginTime += 0.5; } visual.Children.Add(seriesCanvas); } // Label animation if (animationEnabled && _tempDataSeries != null) _tempDataSeries.Storyboard = AnimationHelper.ApplyOpacityAnimation(labelCanvas, _tempDataSeries, _tempDataSeries.Storyboard, animationBeginTime, 1, 1); visual.Children.Add(labelCanvas); RectangleGeometry clipRectangle = new RectangleGeometry(); clipRectangle.Rect = new Rect(0, -chart.ChartArea.PLANK_DEPTH, width + chart.ChartArea.PLANK_OFFSET, height + chart.ChartArea.PLANK_DEPTH); visual.Clip = clipRectangle; return visual; } /// /// Recalculate and apply new brush /// /// Shape reference /// New Brush /// Whether lighting is enabled /// Whether 3d effevt is enabled internal static void ReCalculateAndApplyTheNewBrush(DataPoint dataPoint, Shape shape, Brush newBrush, Boolean isLightingEnabled, Boolean is3D) { Brush oCRectfillColor = GetOpenCloseRectangleFillbrush(dataPoint, newBrush); switch ((shape.Tag as ElementData).VisualElementName) { case "HlLine": shape.Stroke = isLightingEnabled ? Graphics.GetLightingEnabledBrush(newBrush, "Linear", null) : Graphics.GetBevelTopBrush(newBrush); break; case "OcRect": shape.Fill = isLightingEnabled ? Graphics.GetLightingEnabledBrush(oCRectfillColor, "Linear", null) : oCRectfillColor; shape.Stroke = GetOpenCloseRectangleBorderbrush(dataPoint, newBrush); break; case "TopBevel": shape.Fill = Graphics.GetBevelTopBrush(oCRectfillColor); break; case "LeftBevel": shape.Fill = Graphics.GetBevelSideBrush((isLightingEnabled ? -70 : 0), oCRectfillColor); break; case "RightBevel": shape.Fill = Graphics.GetBevelSideBrush((isLightingEnabled ? -110 : 180), oCRectfillColor); break; case "BottomBevel": shape.Fill = Graphics.GetBevelSideBrush(90, oCRectfillColor); break; } } /// /// Place label for DataPoint /// /// Visual /// Canvas for label /// DataPoint internal static void PlaceLabel(Canvas visual, Canvas labelCanvas, DataPoint dataPoint) { if ((Boolean)dataPoint.LabelEnabled && !String.IsNullOrEmpty(dataPoint.LabelText)) { Canvas dataPointVisual = dataPoint.Faces.Visual as Canvas; Title tb = new Title() { Text = dataPoint.TextParser(dataPoint.LabelText), InternalFontFamily = dataPoint.LabelFontFamily, InternalFontSize = dataPoint.LabelFontSize.Value, InternalFontWeight = (FontWeight)dataPoint.LabelFontWeight, InternalFontStyle = (FontStyle)dataPoint.LabelFontStyle, InternalBackground = dataPoint.LabelBackground, InternalFontColor = Chart.CalculateDataPointLabelFontColor(dataPoint.Chart as Chart, dataPoint, dataPoint.LabelFontColor, LabelStyles.OutSide) }; tb.CreateVisualObject(new ElementData() { Element = dataPoint }); tb.TextElement.SetValue(Canvas.TopProperty, -(Double)1); Double labelTop; Double labelLeft; if (Double.IsNaN(dataPoint.LabelAngle) || dataPoint.LabelAngle == 0) { labelTop = (Double)dataPointVisual.GetValue(Canvas.TopProperty) - tb.Height; labelLeft = (Double)dataPointVisual.GetValue(Canvas.LeftProperty) + (dataPointVisual.Width - tb.Width) / 2; if (labelTop < 0) labelTop = (Double)dataPointVisual.GetValue(Canvas.TopProperty); if (labelLeft < 0) labelLeft = 1; if (labelLeft + tb.ActualWidth > labelCanvas.Width) labelLeft = labelCanvas.Width - tb.ActualWidth - 2; tb.Visual.SetValue(Canvas.LeftProperty, labelLeft); tb.Visual.SetValue(Canvas.TopProperty, labelTop - 2); } else { Point centerOfRotation = new Point((Double)dataPointVisual.GetValue(Canvas.LeftProperty) + dataPointVisual.Width / 2, (Double)dataPointVisual.GetValue(Canvas.TopProperty)); Double radius = 4; Double angle = 0; Double angleInRadian = 0; if (dataPoint.LabelAngle > 0 && dataPoint.LabelAngle <= 90) { angle = dataPoint.LabelAngle - 180; angleInRadian = (Math.PI / 180) * angle; radius += tb.Width; angle = (angleInRadian - Math.PI) * (180 / Math.PI); } else if (dataPoint.LabelAngle >= -90 && dataPoint.LabelAngle < 0) { angle = dataPoint.LabelAngle; angleInRadian = (Math.PI / 180) * angle; } //else //{ // if (LabelAngle >= -90 && LabelAngle < 0) // { // angle = 180 + LabelAngle; // angleInRadian = (Math.PI / 180) * angle; // radius += TextBlockSize.Width; // angle = (angleInRadian - Math.PI) * (180 / Math.PI); // SetRotation(radius, angle, angleInRadian, centerOfRotation); // } // else if (LabelAngle > 0 && LabelAngle <= 90) // { // angle = LabelAngle; // angleInRadian = (Math.PI / 180) * angle; // SetRotation(radius, angle, angleInRadian, centerOfRotation); // } //} labelLeft = centerOfRotation.X + radius * Math.Cos(angleInRadian); labelTop = centerOfRotation.Y + radius * Math.Sin(angleInRadian); labelTop -= tb.Height / 2; tb.Visual.SetValue(Canvas.LeftProperty, labelLeft); tb.Visual.SetValue(Canvas.TopProperty, labelTop); tb.Visual.RenderTransformOrigin = new Point(0, 0.5); tb.Visual.RenderTransform = new RotateTransform() { CenterX = 0, CenterY = 0, Angle = angle }; } labelCanvas.Children.Add(tb.Visual); dataPoint.LabelVisual = tb.Visual; } } #endregion #region Internal Events And Delegates #endregion #region Data /// /// Width of a DataPoint /// #cbcbcb private static Double _dataPointWidth; internal static Brush _shadowColor = new SolidColorBrush(Color.FromArgb((Byte)0xff, (Byte)0xb0, (Byte)0xb0, (Byte)0xb0)); internal static Double _shadowDepth = 1.5; #endregion } }