using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using TradeIdeas.TIProData.Interfaces; // This file contains a variety of miscellaneous support classes for ConfigDemo lessons. namespace TradeIdeas.TIProGUI.CBT { /// /// Tab sheets can be created in other classes and files. This is what the guts knows /// about its parent. It doesn't directly know or care about the ConfigDemo class. I /// don't expect other classes to implement this interface, but this keeps the /// interface simpler and better documented. /// public interface IContainer { /// /// Which filter are we looking at? /// string InternalCode { get; } /// /// The text description of the field, like "Price ($)" or "Bid Size (Shares)". /// string Description { get; } IConnectionMaster ConnectionMaster { get; } /// /// Read and parse the min value from the text box. Do it in a standard way. /// double? MinValue { get; } /// /// Read and parse the max value from the text box. Do it in a standard way. /// double? MaxValue { get; } /// /// Use this to set the value, or to listen for changes to the value. /// is the preferred way to read this value. /// TextBox MinTextBox { get; } /// /// Use this to set the value, or to listen for changes to the value. /// is the preferred way to read this value. /// TextBox MaxTextBox { get; } /// /// Switch to a tab page which is good at showing an example. This is mostly /// aimed at the class. That page is good at listing /// examples, but not at showing the results. /// void ShowExample(); } /// /// Each tab sheet we create should return one of these. /// public interface IChild { /// /// The user friendly name to display on the tab sheet. /// string Name { get; } /// /// This will be added to the tab sheet. /// Presumably the object will be returning the this pointer with a cast. /// will probably create the /// Control Body { get; } /// /// After the user sets an example, is it reasonable to show the example /// on this tab page? Ideally we would show multiple views, perhaps all /// the views, at once. Since we can only show one at a time, what is /// the best place to jump after someone clicks on an example. /// bool CanShowExample { get; } /// /// This event is fired any time the child is made visible. It would be /// very hard to set up a traditional C# event, so instead the container /// just calls this every time. /// void OnShow(); } public delegate IChild Creator(IContainer container); public class Translator { public double M { get; set; } public double B { get; set; } public double Get(double x) { return M * x + B; } public double[] Get(double low, double high, double open, double close) { return new double[4] { Get(low), Get(high), Get(open), Get(close) }; } public double[] GetSingle(double x) { return new double[1] { Get(x) }; } public Translator() { M = 1; B = 0; } private static Random _random = new Random(); /// /// This is aimed at filters where the scale doesn't matter at all. /// public void RandomScale() { B = _random.Next(100) + 1; switch (_random.Next(4)) { case 0: M = 1; break; case 1: M = 2; break; case 2: M = 5; break; case 3: M = 10; break; } } } public class MinMaxSummary { public Chart Chart { get; private set; } public ChartArea ChartArea { get; private set; } private int _x; Series _placeHolder; public int X { get { return _x; } set { _x = value; // If we try to draw this too far to the right, off the end of the chart, // things start to act strange. So I'm creating a placeholder to make sure // we have enough room. if (null == _placeHolder) { _placeHolder = new Series(); Chart.Series.Add(_placeHolder); _placeHolder.ChartArea = ChartArea.Name; } else { _placeHolder.Points.Clear(); } for (int i = 0; i <= value; i++) { DataPoint dataPoint = new DataPoint(); dataPoint.IsEmpty = true; _placeHolder.Points.Add(dataPoint); } } } public double? MinValue { get; set; } public double? MaxValue { get; set; } public MinMaxSummary(Chart chart, ChartArea chartArea = null) { Chart = chart; if (null == chartArea) ChartArea = chart.ChartAreas[0]; else ChartArea = chartArea; if (chart.Series.Count > 0) X = chart.Series[0].Points.Count; } private List _annotations = new List(); public void Hide() { foreach (Annotation annotation in _annotations) { Chart.Annotations.Remove(annotation); annotation.Dispose(); } _annotations.Clear(); } private static readonly Color MIN_COLOR = Color.FromArgb(0, 192, 192); private static readonly Color MAX_COLOR = Color.FromArgb(158, 0, 192); private static readonly Color NEUTRAL_COLOR = Color.FromArgb(79, 96, 192); private static readonly AnnotationSmartLabelStyle SMART_LABEL_STYLE = new AnnotationSmartLabelStyle(); static MinMaxSummary() { SMART_LABEL_STYLE.IsMarkerOverlappingAllowed = true; // AllowOutsidePlotArea does not do what I would expect. The chart never displays something // off the screen. If something would have been off the screen, we have two options, move it // slightly, so it is entirely on the screen or don't show it. That's what this property // chooses between. //SMART_LABEL_STYLE.AllowOutsidePlotArea = LabelOutsidePlotAreaStyle.Yes; // MaxMovingDistance doesn't do what I'd hoped. It only chooses between trying to move // something so it's all visible, or not displaying it at all. //SMART_LABEL_STYLE.MaxMovingDistance = 0; // Ideally i'd be able to show something that is partially off the screen. That would make // the code easier because I wouldn't have to check for that case myself. I'd like it to just // be clipped. I can deal with that. The bigger issue seems to be roundoff error. Sometimes // things right at the edge of the screen seem to appear and disappear, or move, at seemingly // random times. } private void CommonInit(Annotation annotation) { annotation.AnchorX = _x + 1; annotation.IsSizeAlwaysRelative = false; annotation.AxisX = ChartArea.AxisX; annotation.AxisY = ChartArea.AxisY; //annotation.ClipToChartArea = ChartArea.Name; annotation.SmartLabelStyle = SMART_LABEL_STYLE; //annotation.SmartLabelStyle = new AnnotationSmartLabelStyle(); //annotation.SmartLabelStyle.IsMarkerOverlappingAllowed = true; _annotations.Add(annotation); Chart.Annotations.Add(annotation); } private void DrawMax() { Axis axisY = ChartArea.AxisY; double value = MaxValue.Value; if ((value >= axisY.Minimum) && (value <= axisY.Maximum)) { HorizontalLineAnnotation line = new HorizontalLineAnnotation(); line.AnchorY = value; line.LineColor = MAX_COLOR; line.LineWidth = 3; line.Width = 0.7; line.AnchorAlignment = ContentAlignment.MiddleCenter; CommonInit(line); } if (value >= axisY.Minimum) { // It seems that if an annotation is partially off the screen, it isn't // drawn at all. So we manuall cut off anything that would be out of bounds, so // the rest will be visible. Oddly enough, setting // SMART_LABEL_STYLE.AllowOutsidePlotArea does not seem to have any effect. double top = Math.Min(axisY.Maximum, value); ArrowAnnotation arrow = new ArrowAnnotation(); arrow.AnchorY = top; arrow.BackColor = MAX_COLOR; arrow.LineWidth = 0; arrow.Height = top - axisY.Minimum; arrow.Width = 0; arrow.AnchorAlignment = ContentAlignment.BottomCenter; CommonInit(arrow); } } private void DrawMin() { Axis axisY = ChartArea.AxisY; double value = MinValue.Value; if ((value >= axisY.Minimum) && (value <= axisY.Maximum)) { HorizontalLineAnnotation line = new HorizontalLineAnnotation(); line.AnchorY = value; line.LineColor = MIN_COLOR; line.LineWidth = 3; line.Width = 0.7; line.AnchorAlignment = ContentAlignment.MiddleCenter; CommonInit(line); } if (value <= axisY.Maximum) { double bottom = Math.Max(axisY.Minimum, value); ArrowAnnotation arrow = new ArrowAnnotation(); // Strange. Most of the time AnchorY works. But it seems to have some issues when // height is negative. Switching from AnchorY to Y seemed to fix everything. //arrow.AnchorY = axisY.Maximum; arrow.Y = axisY.Maximum; arrow.BackColor = MIN_COLOR; arrow.LineWidth = 0; arrow.Height = bottom - axisY.Maximum; // Notice that this is a negative number. arrow.Width = 0; arrow.AnchorAlignment = ContentAlignment.TopCenter; CommonInit(arrow); } } public void Update() { Hide(); Axis axisY = ChartArea.AxisY; if ((null == MinValue) && (null == MaxValue)) { // Everything is allowed. ArrowAnnotation arrow = new ArrowAnnotation(); arrow.AnchorY = axisY.Minimum; arrow.ArrowStyle = ArrowStyle.DoubleArrow; // Left / Center / Right seems to be ignored. Top means to put the // arrow above AnchorY, i.e. AnchorY touches the bottom of the arrow. // Bottom means to put the arrow below AnchorY, i.e. AnchorY touches // the top of the arrow. (This seems to be reversed when the height // is a negative number. Although, I seem to have a lot of problems // when height is a negative number.) arrow.AnchorAlignment = ContentAlignment.TopCenter; arrow.BackColor = NEUTRAL_COLOR; arrow.LineWidth = 0; arrow.Height = axisY.Maximum - axisY.Minimum; arrow.Width = 0; CommonInit(arrow); } else if (null == MinValue) // Max is set and min is not. DrawMax(); else if (null == MaxValue) // Min is set and max is not. DrawMin(); else if (MinValue.Value == MaxValue.Value) { // One point HorizontalLineAnnotation line = new HorizontalLineAnnotation(); line.AnchorY = MinValue.Value; line.AnchorAlignment = ContentAlignment.MiddleCenter; line.LineColor = NEUTRAL_COLOR; line.LineWidth = 3; line.Width = 0.7; CommonInit(line); } else if (MinValue.Value < MaxValue.Value) { // Inside range. VerticalLineAnnotation middleLine = new VerticalLineAnnotation(); double top = Math.Min(axisY.Maximum, MaxValue.Value); double bottom = Math.Max(axisY.Minimum, MinValue.Value); middleLine.AnchorY = top; middleLine.AnchorAlignment = ContentAlignment.BottomCenter; middleLine.LineColor = NEUTRAL_COLOR; middleLine.LineWidth = 10; middleLine.Height = top - bottom; CommonInit(middleLine); HorizontalLineAnnotation minLine = new HorizontalLineAnnotation(); minLine.AnchorY = MinValue.Value; minLine.LineColor = MIN_COLOR; minLine.LineWidth = 3; minLine.Width = 0.7; minLine.AnchorAlignment = ContentAlignment.MiddleCenter; CommonInit(minLine); HorizontalLineAnnotation maxLine = new HorizontalLineAnnotation(); maxLine.AnchorY = MaxValue.Value; maxLine.LineColor = MAX_COLOR; maxLine.LineWidth = 3; maxLine.Width = 0.7; maxLine.AnchorAlignment = ContentAlignment.MiddleCenter; CommonInit(maxLine); } else { // Outside range. DrawMin(); DrawMax(); } } } }