using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; using System.Collections.Specialized; using System.Windows; using System.Windows.Controls; using System.ComponentModel; using System.Diagnostics; using Microsoft.Research.DynamicDataDisplay.Common; namespace Microsoft.Research.DynamicDataDisplay.Charts.Markers { public class MarkerChart : PlotterElement { /// /// Initializes a new instance of the class. /// public MarkerChart() { Viewport2D.SetIsContentBoundsHost(this, true); } #region DataSource private IEnumerable dataSource; /// /// Gets or sets the data source. /// Value can be null. /// /// The data source. public IEnumerable DataSource { get { return dataSource; } set { DetachDataSource(); dataSource = value; AttachDataSource(); OnDataSourceChanged(); RebuildMarkers(true); } } protected virtual void OnDataSourceChanged() { } protected virtual void RebuildMarkers(bool shouldReleaseMarkers) { if (markerGenerator == null) return; if (shouldReleaseMarkers) { foreach (FrameworkElement item in itemsPanel.Children) { var enumerator = item.GetLocalValueEnumerator(); while (enumerator.MoveNext()) { item.ClearValue(enumerator.Current.Property); } descriptor.RemoveValueChanged(item, OnMarkerViewportBoundsChanged); markerGenerator.ReleaseMarker(item); } } itemsPanel.Children.Clear(); if (dataSource == null) return; IndividualArrangePanel specialPanel = itemsPanel as IndividualArrangePanel; if (specialPanel != null) specialPanel.BeginBatchAdd(); foreach (object item in dataSource) { var marker = CreateMarker(item); itemsPanel.Children.Add(marker); } if (specialPanel != null) specialPanel.EndBatchAdd(); RecalculateViewportBounds(); } private FrameworkElement CreateMarker(object item) { var marker = markerGenerator.CreateMarker(item); if (marker != null) { marker.DataContext = item; AttachViewportChangedListener(marker); BindMarkerEventArgs bindArgs = new BindMarkerEventArgs { Data = item, Marker = marker }; OnMarkerBind(bindArgs); if (markerBindCallback != null) { markerBindCallback(bindArgs); } } return marker; } protected virtual void OnMarkerBind(BindMarkerEventArgs e) { } private Action markerBindCallback = null; public Action MarkerBindCallback { get { return markerBindCallback; } set { if (markerBindCallback != value) { markerBindCallback = value; RebuildMarkers(false); } } } private void AttachDataSource() { INotifyCollectionChanged notifyingCollection = dataSource as INotifyCollectionChanged; if (notifyingCollection != null) { notifyingCollection.CollectionChanged += OnDataSourceCollectionChanged; } } private void OnDataSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { int index = e.OldStartingIndex; foreach (object item in e.OldItems) { var oldMarker = itemsPanel.Children[index]; if (markerGenerator != null) { FrameworkElement element = (FrameworkElement)oldMarker; // todo call cleanup callback here markerGenerator.ReleaseMarker(element); descriptor.RemoveValueChanged(element, OnMarkerViewportBoundsChanged); } itemsPanel.Children.RemoveAt(index); } } if (e.NewItems != null) { int index = e.NewStartingIndex; foreach (object item in e.NewItems) { var marker = CreateMarker(item); itemsPanel.Children.Insert(index, marker); index++; } } RecalculateViewportBounds(); } DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(ViewportPanel.ActualViewportBoundsProperty, typeof(FrameworkElement)); private void AttachViewportChangedListener(FrameworkElement element) { // todo or use this, or remove this //descriptor.RemoveValueChanged(element, OnMarkerViewportBoundsChanged); //descriptor.AddValueChanged(element, OnMarkerViewportBoundsChanged); } private Rect viewportBounds = Rect.Empty; private void OnMarkerViewportBoundsChanged(object sender, EventArgs e) { RecalculateViewportBounds(); FrameworkElement element = (FrameworkElement)sender; DataRect elementBounds = ViewportPanel.GetActualViewportBounds(element); DataRect prevElementBounds = ViewportPanel.GetPrevActualViewportBounds(element); } private void RecalculateViewportBounds() { DataRect bounds = DataRect.Empty; foreach (UIElement item in itemsPanel.Children) { DataRect elementBounds = ViewportPanel.GetActualViewportBounds(item); bounds.Union(elementBounds); } Viewport2D.SetContentBounds(this, bounds); } private void DetachDataSource() { INotifyCollectionChanged notifyingCollection = dataSource as INotifyCollectionChanged; if (notifyingCollection != null) { notifyingCollection.CollectionChanged -= OnDataSourceCollectionChanged; } } #endregion #region Marker private OldMarkerGenerator markerGenerator; public OldMarkerGenerator MarkerGenerator { get { return markerGenerator; } set { if (value == null) throw new ArgumentNullException("value"); if (markerGenerator != value) { DetachMarkerGenerator(); markerGenerator = value; AttachMarkerGenerator(); RebuildMarkers(false); } } } private void AttachMarkerGenerator() { markerGenerator.Changed += OnMarkerChanged; } private void OnMarkerChanged(object sender, EventArgs e) { RebuildMarkers(false); } private void DetachMarkerGenerator() { if (markerGenerator != null) { markerGenerator.Changed -= OnMarkerChanged; } } #endregion #region Plotter attaching private ViewportPanel itemsPanel = new ViewportPanel(); protected Panel ItemsPanel { get { return itemsPanel; } } private Plotter2D plotter; protected override void OnPlotterAttached(Plotter plotter) { base.OnPlotterAttached(plotter); this.plotter = (Plotter2D)plotter; ((IPlotterElement)itemsPanel).OnPlotterAttached(plotter); } protected override void OnPlotterDetaching(Plotter plotter) { ((IPlotterElement)itemsPanel).OnPlotterDetaching(plotter); this.plotter = null; base.OnPlotterDetaching(plotter); } #endregion } }