using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.ComponentModel; using System.Windows.Controls; using Infragistics.Controls.Charts; using System.Windows.Shapes; using System.Windows.Input; using Infragistics.Windows; using System.Windows.Data; using System.Windows.Media; using System.Windows.Media.Effects; using System.Collections.ObjectModel; using System.Windows.Controls.Primitives; namespace SingleWellApp4.Converters { public class CustomCrossHairBehavior : DependencyObject { public static readonly DependencyProperty CustomCrossHairProperty = DependencyProperty.RegisterAttached("CustomCrossHair", typeof(CustomCrossHairs), typeof(CustomCrossHairBehavior), new PropertyMetadata(null, (o, e) => { CustomCrossHairChanged(o as XamDataChart, e.OldValue as CustomCrossHairs, e.NewValue as CustomCrossHairs); })); public static CustomCrossHairs GetCustomCrossHair(DependencyObject target) { return target.GetValue(CustomCrossHairProperty) as CustomCrossHairs; } public static void SetCustomCrossHair(DependencyObject target, CustomCrossHairs value) { target.SetValue(CustomCrossHairProperty, value); } public static void CustomCrossHairChanged( XamDataChart chart, CustomCrossHairs oldVal, CustomCrossHairs newVal) { if (chart == null) { return; } if (oldVal != null) { oldVal.DetachOwner(chart); } if (newVal != null) { newVal.AttachOwner(chart); } } //CursorTooltip public static readonly DependencyProperty CursorTooltipProperty = DependencyProperty.RegisterAttached("CursorTooltip", typeof(CursorTooltipBehavior), typeof(CustomCrossHairBehavior), new PropertyMetadata(null, (o, e) => CursorTooltipChanged(o as XamDataChart, e.OldValue as CursorTooltipBehavior, e.NewValue as CursorTooltipBehavior))); public static CursorTooltipBehavior GetCursorTooltip(DependencyObject target) { return target.GetValue(CursorTooltipProperty) as CursorTooltipBehavior; } public static void SetCursorTooltip(DependencyObject target, CursorTooltipBehavior behavior) { target.SetValue(CursorTooltipProperty, behavior); } private static void CursorTooltipChanged(XamDataChart chart, CursorTooltipBehavior oldValue, CursorTooltipBehavior newValue) { if (chart == null) return; if (oldValue != null) { oldValue.OnDetach(chart); } if (newValue != null) { newValue.OnAttach(chart); } } } public class CustomCrossHairs : DependencyObject, INotifyPropertyChanged { #region Private Fields private Canvas _container; private XamDataChart _owner; private Panel _lastMarkerContainer; private object _currentData; private Path _startLine; private Path _endLine; #endregion #region Initializing methods public void AttachOwner(XamDataChart chart) { if (_owner != null) { DetachOwner(_owner); } _owner = chart; _owner.LayoutUpdated += _owner_LayoutUpdated; _owner.MouseMove += _owner_MouseMove; _owner.MouseEnter += _owner_MouseEnter; _owner.MouseLeave += _owner_MouseLeave; _owner.MouseDoubleClick += _owner_MouseDoubleClick; _owner.MouseDown += _owner_MouseDown; _container = new Canvas(); Panel.SetZIndex(_container, 1000); _owner.Dispatcher.BeginInvoke(new Action(() => { //DependencyObject contentPresenter = Utilities.GetDescendantFromName(_owner, "ContentPresenter"); //if (contentPresenter != null) //{ //Border chartContent = Utilities.GetDescendantFromType // (contentPresenter, typeof(Border), false) as Border; //(chartContent.Child as Grid).Children.Add(_container); _startLine = new Path(); _endLine = new Path(); InitializePathBindings(_startLine); InitializePathBindings(_endLine); _container.Children.Add(_startLine); _container.Children.Add(_endLine); }), System.Windows.Threading.DispatcherPriority.Background, null); } public void DetachOwner(XamDataChart chart) { if (_owner != chart) return; chart.MouseMove -= _owner_MouseMove; chart.MouseLeave -= _owner_MouseLeave; chart.MouseEnter -= _owner_MouseEnter; chart.LayoutUpdated -= _owner_LayoutUpdated; chart.MouseDoubleClick -= _owner_MouseDoubleClick; chart.MouseDown -= _owner_MouseDown; Grid parent = _container.Parent as Grid; parent.Children.Remove(_container); _owner = null; } private void InitializePathBindings(Path path) { string prop = "VerticalCrossHairSettings."; path.SetBinding(Path.StrokeProperty, new Binding { Source = this, Path = new PropertyPath(prop + "Stroke") }); path.SetBinding(Path.StrokeDashArrayProperty, new Binding { Source = this, Path = new PropertyPath(prop + "DashArray") }); path.SetBinding(Path.StrokeThicknessProperty, new Binding { Source = this, Path = new PropertyPath(prop + "Thickness") }); path.SetBinding(Path.EffectProperty, new Binding { Source = this, Path = new PropertyPath(prop + "Effect") }); path.SetBinding(Path.VisibilityProperty, new Binding { Source = this, Path = new PropertyPath(prop + "Visibility") }); } #endregion #region Event Handlers void _owner_LayoutUpdated(object sender, EventArgs e) { if (_container.Parent == null) { ContentPresenter cp = Utilities.GetDescendantFromName(_owner, "ContentPresenter") as ContentPresenter; if (cp == null) return; Border chartContent = Utilities.GetDescendantFromType( cp, typeof(Border), false) as Border; if (chartContent == null) return; (chartContent.Child as Grid).Children.Add(_container); } Refresh(); } void _owner_MouseMove(object sender, MouseEventArgs e) { Marker marker = Utilities.GetAncestorFromType(e.OriginalSource as DependencyObject, typeof(Marker), false) as Marker; if (marker != null) { _lastMarkerContainer = marker.Parent as Panel; _currentData = (marker.Content as DataContext).Item; Refresh(); } } void _owner_MouseEnter(object sender, MouseEventArgs e) { _startLine.Opacity = 1; // _endLine.Opacity = 1; _endLine.Opacity = 1; } void _owner_MouseLeave(object sender, MouseEventArgs e) { if (_startLine == null || _endLine == null) return; _startLine.Opacity = 0; _endLine.Opacity = 0; } //DoubleClick will toggle show/off 2 crosshairs. void _owner_MouseDoubleClick(object sender, MouseEventArgs e) { if (!_bShow2Crosshairs) { Point mousePoint = e.GetPosition(_container); StartLineX = mousePoint.X; _bShow2Crosshairs = true; } else _bShow2Crosshairs = false; } void _owner_MouseDown(object sender, MouseEventArgs e) { } #endregion bool _bShow2Crosshairs = false; double StartLineX; #region Helper Methods private void Refresh() { if (_startLine == null || _endLine == null) return; Point mousePoint = Mouse.GetPosition(_container); _endLine.Data = new LineGeometry( new Point(mousePoint.X, 0), new Point(mousePoint.X, _container.ActualHeight)); if (_bShow2Crosshairs) _startLine.Data = new LineGeometry(new Point(StartLineX, 0), new Point(StartLineX, _container.ActualHeight)); else _startLine.Data = _endLine.Data; } #endregion #region Properties #region Horizontal Settings public CustomCrossHairSettings HorizontalCrossHairSettings { get { return (CustomCrossHairSettings)GetValue(HorizontalCrossHairSettingsProperty); } set { SetValue(HorizontalCrossHairSettingsProperty, value); } } // Using a DependencyProperty as the backing store for HorizontalCrossHairSettings. This enables animation, styling, binding, etc... public static readonly DependencyProperty HorizontalCrossHairSettingsProperty = DependencyProperty.Register("HorizontalCrossHairSettings", typeof(CustomCrossHairSettings), typeof(CustomCrossHairs), new PropertyMetadata(null)); #endregion #region Vertical Settings public CustomCrossHairSettings VerticalCrossHairSettings { get { return (CustomCrossHairSettings)GetValue(VerticalCrossHairSettingsProperty); } set { SetValue(VerticalCrossHairSettingsProperty, value); } } // Using a DependencyProperty as the backing store for VerticalCrossHairSettings. This enables animation, styling, binding, etc... public static readonly DependencyProperty VerticalCrossHairSettingsProperty = DependencyProperty.Register("VerticalCrossHairSettings", typeof(CustomCrossHairSettings), typeof(CustomCrossHairs), new PropertyMetadata(null)); #endregion #region Horizontal Range public TimeSpan HorizontalCrosshairRange { get { return (TimeSpan)GetValue(HorizontalCrosshairRangeProperty); } set { SetValue(HorizontalCrosshairRangeProperty, value); } } // Using a DependencyProperty as the backing store for HorizontalCrosshairRange. This enables animation, styling, binding, etc... public static readonly DependencyProperty HorizontalCrosshairRangeProperty = DependencyProperty.Register("HorizontalCrosshairRange", typeof(TimeSpan), typeof(CustomCrossHairs), new UIPropertyMetadata(TimeSpan.FromHours(0))); #endregion #region Vertical Range public double VerticalCrosshairRange { get { return (double)GetValue(VerticalCrosshairRangProperty); } set { SetValue(VerticalCrosshairRangProperty, value); } } // Using a DependencyProperty as the backing store for VerticalCrosshairRang. This enables animation, styling, binding, etc... public static readonly DependencyProperty VerticalCrosshairRangProperty = DependencyProperty.Register("VerticalCrosshairRang", typeof(double), typeof(CustomCrossHairs), new UIPropertyMetadata(0.0)); #endregion #endregion #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } #endregion } public class CustomCrossHairSettings : DependencyObject { public double Thickness { get { return (double)GetValue(ThicknessProperty); } set { SetValue(ThicknessProperty, value); } } // Using a DependencyProperty as the backing store for Thickness. This enables animation, styling, binding, etc... public static readonly DependencyProperty ThicknessProperty = DependencyProperty.Register("Thickness", typeof(double), typeof(CustomCrossHairSettings), new UIPropertyMetadata(1.0)); public Brush Stroke { get { return (Brush)GetValue(StrokeProperty); } set { SetValue(StrokeProperty, value); } } // Using a DependencyProperty as the backing store for Stroke. This enables animation, styling, binding, etc... public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register("Stroke", typeof(Brush), typeof(CustomCrossHairSettings), new UIPropertyMetadata(Brushes.Gray)); public DoubleCollection DashArray { get { return (DoubleCollection)GetValue(DashArrayProperty); } set { SetValue(DashArrayProperty, value); } } // Using a DependencyProperty as the backing store for DashArray. This enables animation, styling, binding, etc... public static readonly DependencyProperty DashArrayProperty = DependencyProperty.Register("DashArray", typeof(DoubleCollection), typeof(CustomCrossHairSettings), new UIPropertyMetadata(null)); public Effect Effect { get { return (Effect)GetValue(EffectProperty); } set { SetValue(EffectProperty, value); } } // Using a DependencyProperty as the backing store for Effect. This enables animation, styling, binding, etc... public static readonly DependencyProperty EffectProperty = DependencyProperty.Register("Effect", typeof(Effect), typeof(CustomCrossHairSettings), new UIPropertyMetadata(null)); public Visibility Visibility { get { return (Visibility)GetValue(VisibilityProperty); } set { SetValue(VisibilityProperty, value); } } // Using a DependencyProperty as the backing store for Visibility. This enables animation, styling, binding, etc... public static readonly DependencyProperty VisibilityProperty = DependencyProperty.Register("Visibility", typeof(Visibility), typeof(CustomCrossHairSettings), new UIPropertyMetadata(Visibility.Visible)); } public class SeriesItemInfo : INotifyPropertyChanged { private Series _series; private object _item; //show the detlaValue in crosshair tooltip private string _deltaValue; public Series Series { get { return _series; } set { _series = value; OnPropertyChanged("Series"); } } public object Item { get { return _item; } set { _item = value; OnPropertyChanged("Item"); } } public string DeltaValue { get { return _deltaValue; } set { _deltaValue = value; OnPropertyChanged("DeltaValue"); } } public float _line1Value { get; set; } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } #endregion } public class SeriesItemInfoCollection : ObservableCollection, INotifyPropertyChanged { private bool _showDeltaValue; public bool ShowDeltaValue { get { return _showDeltaValue; } set { _showDeltaValue = value; OnPropertyChanged("ShowDeltaValue"); } } private DateTime timeStamp; public DateTime TimeStamp { get { return timeStamp; } set { timeStamp = value; OnPropertyChanged("TimeStamp"); } } private TimeSpan deltaTime; public TimeSpan DeltaTime { get { return deltaTime; } set { deltaTime = value; OnPropertyChanged("DeltaTime"); } } private string deltaTimeString; public string DeltaTimeString { get { return deltaTimeString; } set { deltaTimeString = value; OnPropertyChanged("DeltaTimeString"); } } private DateTime line1Time; public DateTime Line1Time { get { return line1Time; } set { line1Time = value; OnPropertyChanged("Line1Time"); } } //End test //public void UpdateSeriesItem(Series series, object item) //{ // bool found = false; // var indexes = from curr in this // where curr.Series == series // select this.IndexOf(curr); // foreach (int index in indexes) // { // found = true; // this[index].Item = item; // if (this[index].ShowDeltaValue) // { // float deltaV = (float)this[index].Item - this[index]._line1Value; // this[index].DeltaValue = " Diff: " + deltaV.ToString(); // } // else // { // this[index].DeltaValue = ""; // } // } // if (!found) // { // this.Add(new SeriesItemInfo() { Series = series, Item = item, ShowDeltaValue = false}); // } //} public void UpdateSeriesItem(Series series, object item, DateTime cursorTimestamp) { TimeStamp = cursorTimestamp; if (ShowDeltaValue) { DeltaTime = TimeStamp - Line1Time; DeltaTimeString = " Diff: " + DeltaTime.ToString(@"dd\.hh\:mm\:ss"); } else { DeltaTimeString = ""; } bool found = false; var indexes = from curr in this where curr.Series == series select this.IndexOf(curr); foreach (int index in indexes) { found = true; this[index].Item = item; if (ShowDeltaValue) { float deltaV = (float)this[index].Item - this[index]._line1Value; this[index].DeltaValue = " Diff: " + deltaV.ToString(); // this[index].CursorPosition = "X: " + p.X + " Y: " + p.Y; } else { this[index].DeltaValue = ""; } } if (!found) { this.Add(new SeriesItemInfo() { Series = series, Item = item }); } } public void UpdateSeriesItem(Series series, object item, string delta) { bool found = false; var indexes = from curr in this where curr.Series == series select this.IndexOf(curr); foreach (int index in indexes) { found = true; this[index].Item = item; this[index].DeltaValue = delta; } if (!found) { this.Add(new SeriesItemInfo() { Series = series, Item = item, DeltaValue=delta }); } } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } #endregion } public class CursorTooltipBehavior { private bool _isOverChart = false; private Popup _popup = new Popup(); private ContentControl _content = new ContentControl(); private Panel _container; private XamDataChart _owner = null; private bool _bShowDeltaValue = false; private DataTemplate _tooltipTemplate; private SeriesItemInfoCollection _items = new SeriesItemInfoCollection(); public DataTemplate TooltipTemplate { get { return _tooltipTemplate; } set { _tooltipTemplate = value; _content.ContentTemplate = _tooltipTemplate; } } protected bool IsOverChart { get { return _isOverChart; } set { bool last = _isOverChart; _isOverChart = value; if (_isOverChart && !last) { ShowPopup(); } if (!_isOverChart && last) { HidePopup(); } } } private void HidePopup() { _popup.IsOpen = false; } private void ShowPopup() { _popup.Placement = PlacementMode.RelativePoint; _popup.IsOpen = true; } public void OnAttach(XamDataChart chart) { if (_owner != null) OnDetach(_owner); chart.MouseLeave += Chart_MouseLeave; chart.MouseMove += Chart_MouseMove; chart.MouseDoubleClick += Chart_MouseDoubleClick; chart.SeriesCursorMouseMove += Chart_SeriesCursorMouseMove; _popup.IsOpen = false; _popup.Child = _content; _content.ContentTemplate = TooltipTemplate; _content.Content = _items; if (chart.Parent != null && chart.Parent is Panel) { _container = chart.Parent as Panel; _container.Children.Add(_popup); } } public void OnDetach(XamDataChart chart) { if (_owner != chart) { return; } chart.MouseLeave -= Chart_MouseLeave; chart.MouseMove -= Chart_MouseMove; chart.SeriesCursorMouseMove -= Chart_SeriesCursorMouseMove; IsOverChart = false; _items.Clear(); _owner = null; } void Chart_MouseMove(object sender, MouseEventArgs e) { _popup.PlacementTarget = sender as XamDataChart; SetPopupOffsets(e.GetPosition(sender as XamDataChart)); IsOverChart = true; } void Chart_MouseDoubleClick(object sender, MouseEventArgs e) { if (!_bShowDeltaValue) { _bShowDeltaValue = true; _items.ShowDeltaValue = true; _items.Line1Time = _items.TimeStamp; foreach (SeriesItemInfo si in _items) { si._line1Value = (float)si.Item; // si.ShowDeltaValue = true; } } else { _bShowDeltaValue = false; _items.ShowDeltaValue = false; foreach (SeriesItemInfo si in _items) { si._line1Value = (float)si.Item; // si.ShowDeltaValue = false; } } } private void SetPopupOffsets(Point point) { _popup.VerticalOffset = point.Y + 10; _popup.HorizontalOffset = point.X + 10; } void Chart_SeriesCursorMouseMove( object sender, ChartCursorEventArgs e) { XamDataChart thisChart = e.SeriesViewer as XamDataChart; DateTime chartStartTime = (DateTime)((CategoryDateTimeXAxis)thisChart.Axes[0]).MinimumValue; DateTime chartEndTime = (DateTime)((CategoryDateTimeXAxis)thisChart.Axes[0]).MaximumValue; double x = e.SeriesViewer.CrosshairPoint.X; if (double.IsNaN(x)) return; DateTime cursorTime = chartStartTime.AddSeconds((chartEndTime - chartStartTime).TotalSeconds * x); _items.TimeStamp = cursorTime; if (e.Series != null && e.Item != null) { _items.UpdateSeriesItem(e.Series, e.Item, cursorTime); // _items.ShowCrosshairPosition(e.SeriesViewer.CrosshairPoint.X, e.SeriesViewer.CrosshairPoint.Y); } } void Chart_MouseLeave( object sender, MouseEventArgs e) { IsOverChart = false; } } }