using SciChart.Charting.ChartModifiers; using SciChart.Charting.Visuals.Annotations; using SciChart.Core.Utility.Mouse; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System; namespace Charting.V2.CustomModifiers { /// /// Class XABCDModifier. /// /// /// This class enabled creating consecutive annotations which stay connected to each other. /// /// public class XABCDModifier : ChartModifierBase { /// /// Gets or sets a value indicating whether [close on completion]. /// /// true if [close on completion]; otherwise, false. public bool CloseOnCompletion { get { return (bool)GetValue(CloseOnCompletionProperty); } set { SetValue(CloseOnCompletionProperty, value); } } // Using a DependencyProperty as the backing store for CloseOnCompletion. This enables animation, styling, binding, etc... public static readonly DependencyProperty CloseOnCompletionProperty = DependencyProperty.Register("CloseOnCompletion", typeof(bool), typeof(XABCDModifier), new PropertyMetadata(null)); #region Public Fields /// /// The annotation identifier. This is intended to be used as a filter for CRUD of XABCD patterns. /// public const string ANNOTATION_IDENTIFIER = "XABCDPattern"; #endregion Public Fields #region Private Fields /// /// The current annotation. /// private AnnotationBase _currentAnnotation; /// /// The is creation active. /// private bool _isCreationActive = false; /// /// The is in draw mode. /// private bool _isInDrawMode = false; /// /// The linked lines. /// private IDictionary _linkedLines = new Dictionary(); /// /// The previous annotation. /// private AnnotationBase _previousAnnotation; #endregion Private Fields #region Public Methods /// /// Called when the element is attached to the Chart Surface /// public override void OnAttached() { base.OnAttached(); // set for testing purposes. this would normally be set via a view model or databound in a view. this.CloseOnCompletion = false; } public override void OnDetached() { base.OnDetached(); foreach (AnnotationBase annotation in ParentSurface.Annotations) { if ((string)annotation.Tag == ANNOTATION_IDENTIFIER) { annotation.Selected -= OnLineSelected; } } } /// /// Called when a Mouse DoubleClick occurs on the parent /// /// /// This overridden method is only used to activate/deactive the modifer for testing purposes. /// In a production setting, this modifier will need to be activated via a view model based on appropriate criteria. /// /// Arguments detailing the mouse button operation public override void OnModifierDoubleClick(ModifierMouseArgs e) { base.OnModifierDoubleClick(e); if (_isInDrawMode) { _linkedLines.Clear(); _isCreationActive = false; _currentAnnotation.IsEditable = true; } // turn on and off via double click _isInDrawMode = !_isInDrawMode; } /// /// Called when the KeyDown event is fired for the Master of the current . /// /// /// Used during testing environment to stop creation without a mouse action. /// /// Arguments detailing the key event public override void OnModifierKeyDown(ModifierKeyArgs e) { if (e.Key == Key.Delete) { TryRemoveAnnotation(); } if (e.Key == Key.Escape) { _currentAnnotation.IsEditable = true; _isInDrawMode = false; _isCreationActive = false; _linkedLines.Clear(); return; } if (!_isInDrawMode) return; if (e.Key != Key.LeftCtrl) return; base.OnModifierKeyDown(e); _currentAnnotation.IsEditable = true; _isInDrawMode = false; _isCreationActive = false; if (_linkedLines.Count > 1) { } } /// /// Called when a Mouse Button is pressed on the parent /// /// /// This is the primary method for utilizing this modifier. /// /// Arguments detailing the mouse button operation public override void OnModifierMouseDown(ModifierMouseArgs e) { if (!_isInDrawMode) return; base.OnModifierMouseDown(e); if (_isInDrawMode) { // handle subsequent clicks on an existing annotation if (_isCreationActive) { CompletePatternLeg(e); CreatePatternLeg(e); LinkAnnotations(_currentAnnotation, _previousAnnotation); if (_linkedLines.Count == 5) { CompletePatternLeg(e); CreatePatternLeg(e); LinkAnnotations(_currentAnnotation, _previousAnnotation); _previousAnnotation.X2 = _linkedLines["BC"].X1; _previousAnnotation.Y2 = _linkedLines["BC"].Y1; CompletePatternLeg(e); _currentAnnotation.X1 = _linkedLines["DB"].X2; _currentAnnotation.Y1 = _linkedLines["DB"].Y2; _currentAnnotation.X2 = _linkedLines["XA"].X1; _currentAnnotation.Y2 = _linkedLines["XA"].Y1; // need to bind BX X1Y1 to DB X2Y2 LinkAnnotations(_linkedLines["BX"], _linkedLines["DB"]); LinkAnnotations(_linkedLines["BC"], _linkedLines["DB"]); LinkAnnotations(_linkedLines["BX"], _linkedLines["AB"]); LinkAnnotations(_linkedLines["XA"], _linkedLines["BX"]); CreateDataBinding(_linkedLines["BX"], AnnotationBase.X1Property, _linkedLines["BC"], "X1"); CreateDataBinding(_linkedLines["BX"], AnnotationBase.Y1Property, _linkedLines["BC"], "Y1"); CreateDataBinding(_linkedLines["DB"], AnnotationBase.X2Property, _linkedLines["AB"], "X2"); CreateDataBinding(_linkedLines["DB"], AnnotationBase.Y2Property, _linkedLines["AB"], "Y2"); _previousAnnotation.IsSelected = false; _currentAnnotation.IsEditable = true; _currentAnnotation.IsSelected = false; _isInDrawMode = false; _isCreationActive = false; _linkedLines.Clear(); } } // handle initially creating an annotation else { _isCreationActive = !_isCreationActive; CreatePatternLeg(e); } } } private void OnLineSelected(object sender, EventArgs e) { var line = sender as LineAnnotation; if (line != null) { var tag = (string)line.Tag; } } private void CompletePatternLeg(ModifierMouseArgs e) { //_currentAnnotation.X2 = ParentSurface.XAxis.GetDataValue(e.MousePoint.X); //_currentAnnotation.Y2 = ParentSurface.YAxis.GetDataValue(e.MousePoint.Y); _previousAnnotation = _currentAnnotation; _previousAnnotation.IsEditable = true; // can't be set while placing (interferes with mouse clicks), must be after placed _previousAnnotation.IsSelected = false; } private void CreatePatternLeg(ModifierMouseArgs e) { _currentAnnotation = CreateNewLine(); SetAllCoordinates(_currentAnnotation, e.MousePoint); AddAnnotation(GetXABCDLegName(), _currentAnnotation); } /// /// Called when the Mouse is moved on the parent /// /// /// This is used solely to update the X2Y2 of the annotation being drawn which give real-time visual feed back on placement. /// /// Arguments detailing the mouse move operation public override void OnModifierMouseMove(ModifierMouseArgs e) { if (!_isInDrawMode || !_isCreationActive) return; base.OnModifierMouseMove(e); // give real time feedback on annotation being drawn _currentAnnotation.X2 = ParentSurface.XAxis.GetDataValue(e.MousePoint.X); _currentAnnotation.Y2 = ParentSurface.YAxis.GetDataValue(e.MousePoint.Y); } #endregion Public Methods #region Private Methods /// /// Adds the annotation. /// /// /// The _linkedLines dictionary is used to allow connecting non-consecutive annotations in a group or pattern. /// _linkedLines dictionary is cleared after the XABCDModifier is deactivated. /// /// Name of the leg. /// The annotation. private void AddAnnotation(string legName, AnnotationBase annotation) { ParentSurface.Annotations.Add(annotation); // required for annotation to be rendered on SciChartSurface _linkedLines.Add(legName, annotation); // used internally for advanced/custom annotation linking } /// /// Creates the data binding. /// /// /// To prevent annotations from separating, call this function once for each annotation's properties, i.e. line1's X2Y2 and line2's X1Y1. /// This will allow either annotation itself, or adorner handles to be selected and still keep the annotations connected. /// /// The target. /// The source property. This must be a DependencyProperty. /// The source. /// The string representation of the property to be data bound. private void CreateDataBinding(AnnotationBase target, DependencyProperty targetProperty, AnnotationBase source, string sourceProperty) { Binding binding = new Binding(); binding.Mode = BindingMode.OneWay; binding.Source = source; binding.Path = new PropertyPath(sourceProperty); target.SetBinding(targetProperty, binding); } /// /// Creates the new line. /// /// /// The Tag property is specifically set so these annotations may be differentiated /// from other annotations in . /// When utilizing from another class or resource, the Tag property must be explicitly casted to a string. /// /// This function could be easily extended to support other annotation types by switching on a DependencyProperty /// that specifies the type of annotations this modifier should create. /// /// AnnotationBase. private AnnotationBase CreateNewLine() { /*var gb = new LinearGradientBrush(); gb.MappingMode = BrushMappingMode.RelativeToBoundingBox; gb.StartPoint = new Point(0, 0); gb.EndPoint = new Point(1, 1); gb.GradientStops.Add(new GradientStop(Colors.Blue, 0.0)); gb.GradientStops.Add(new GradientStop(Colors.LawnGreen, 1.0));*/ var line = new LineAnnotation { //Tag = ANNOTATION_IDENTIFIER, Tag = GetXABCDLegName(), StrokeThickness = 2, Stroke = Brushes.LawnGreen, XAxisId = ParentSurface.XAxis.Id, YAxisId = ParentSurface.XAxis.Id }; line.Selected += OnLineSelected; return line; } /// /// Gets the name of the XABCD leg. /// /// System.String. private string GetXABCDLegName() { string result = string.Empty; switch (_linkedLines.Count) { case 0: result = "XA"; break; case 1: result = "AB"; break; case 2: result = "BC"; break; case 3: result = "CD"; break; case 4: result = "DB"; break; case 5: result = "BX"; break; default: result = _linkedLines.Count.ToString(); break; } return result; } /// /// Links the annotations. /// /// /// Helper method to link the beginning of the source annotation to the ending of the target annotation. /// /// The target. /// The source. private void LinkAnnotations(AnnotationBase target, AnnotationBase source) { CreateDataBinding(target, AnnotationBase.X1Property, source, "X2"); CreateDataBinding(target, AnnotationBase.Y1Property, source, "Y2"); CreateDataBinding(source, AnnotationBase.X2Property, target, "X1"); CreateDataBinding(source, AnnotationBase.Y2Property, target, "Y1"); } /// /// Sets all coordinates. /// /// /// Helper method to set the X1Y1 and X2Y2 properties so the annotation can be added to the ParentSurface.Annotations collection. /// /// The annotation. /// The mouse point. private void SetAllCoordinates(AnnotationBase annotation, Point mousePoint) { annotation.X1 = ParentSurface.XAxis.GetDataValue(mousePoint.X); annotation.Y1 = ParentSurface.YAxis.GetDataValue(mousePoint.Y); annotation.X2 = ParentSurface.XAxis.GetDataValue(mousePoint.X); annotation.Y2 = ParentSurface.YAxis.GetDataValue(mousePoint.Y); } /// /// Tries the remove annotation based on if it is selected. Supports multi-delete. /// private void TryRemoveAnnotation() { var selected = ParentSurface.Annotations.Where(a => a.IsSelected).ToArray(); foreach (AnnotationBase annotation in selected) { if (annotation.IsSelected) { ParentSurface.Annotations.Remove(annotation); } } } /// /// Tries the remove annotation. /// /// The annotation. private void TryRemoveAnnotation(AnnotationBase annotation) { ParentSurface.Annotations.Remove(annotation); } #endregion Private Methods } }