using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Media; using Microsoft.Research.DynamicDataDisplay.Charts; using Microsoft.Research.DynamicDataDisplay.Charts.Legend_items; using Microsoft.Research.DynamicDataDisplay.DataSources; using Microsoft.Research.DynamicDataDisplay.Filters; using System.Windows.Shapes; namespace Microsoft.Research.DynamicDataDisplay { /// /// Represents a series of points connected by one polyline. /// public class LineGraph : PointsGraphBase { static LineGraph() { Type thisType = typeof(LineGraph); Legend.DescriptionProperty.OverrideMetadata(thisType, new FrameworkPropertyMetadata("LineGraph")); Legend.LegendItemsBuilderProperty.OverrideMetadata(thisType, new FrameworkPropertyMetadata(new LegendItemsBuilder(DefaultLegendItemsBuilder))); } private static IEnumerable DefaultLegendItemsBuilder(IPlotterElement plotterElement) { LineGraph lineGraph = (LineGraph)plotterElement; Line line = new Line { X1 = 0, Y1 = 10, X2 = 20, Y2 = 0, Stretch = Stretch.Fill, DataContext = lineGraph }; line.SetBinding(Line.StrokeProperty, "Stroke"); line.SetBinding(Line.StrokeThicknessProperty, "StrokeThickness"); Legend.SetVisualContent(lineGraph, line); var legendItem = LegendItemsHelper.BuildDefaultLegendItem(lineGraph); yield return legendItem; } private readonly FilterCollection filters = new FilterCollection(); /// /// Initializes a new instance of the class. /// public LineGraph() { ManualTranslate = true; filters.CollectionChanged += filters_CollectionChanged; } private void filters_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { filteredPoints = null; Update(); } /// /// Initializes a new instance of the class. /// /// The point source. public LineGraph(IPointDataSource pointSource) : this() { DataSource = pointSource; } /// Provides access to filters collection public FilterCollection Filters { get { return filters; } } #region Pen /// /// Gets or sets the brush, using which polyline is plotted. /// /// The line brush. public Brush Stroke { get { return LinePen.Brush; } set { if (LinePen.Brush != value) { if (!LinePen.IsSealed) { LinePen.Brush = value; InvalidateVisual(); } else { Pen pen = LinePen.Clone(); pen.Brush = value; LinePen = pen; } RaisePropertyChanged("Stroke"); } } } /// /// Gets or sets the line thickness. /// /// The line thickness. public double StrokeThickness { get { return LinePen.Thickness; } set { if (LinePen.Thickness != value) { if (!LinePen.IsSealed) { LinePen.Thickness = value; InvalidateVisual(); } else { Pen pen = LinePen.Clone(); pen.Thickness = value; LinePen = pen; } RaisePropertyChanged("StrokeThickness"); } } } /// /// Gets or sets the line pen. /// /// The line pen. [NotNull] public Pen LinePen { get { return (Pen)GetValue(LinePenProperty); } set { SetValue(LinePenProperty, value); } } public static readonly DependencyProperty LinePenProperty = DependencyProperty.Register( "LinePen", typeof(Pen), typeof(LineGraph), new FrameworkPropertyMetadata( new Pen(Brushes.Blue, 1), FrameworkPropertyMetadataOptions.AffectsRender ), OnValidatePen); private static bool OnValidatePen(object value) { return value != null; } #endregion protected override void OnOutputChanged(Rect newRect, Rect oldRect) { filteredPoints = null; base.OnOutputChanged(newRect, oldRect); } protected override void OnDataChanged() { filteredPoints = null; base.OnDataChanged(); } protected override void OnDataSourceChanged(DependencyPropertyChangedEventArgs args) { filteredPoints = null; base.OnDataSourceChanged(args); } protected override void OnVisibleChanged(DataRect newRect, DataRect oldRect) { if (newRect.Size != oldRect.Size) { filteredPoints = null; } base.OnVisibleChanged(newRect, oldRect); } private FakePointList filteredPoints; protected FakePointList FilteredPoints { get { return filteredPoints; } set { filteredPoints = value; } } protected override void UpdateCore() { if (DataSource == null) return; if (Plotter == null) return; Rect output = Viewport.Output; var transform = GetTransform(); if (filteredPoints == null || !(transform.DataTransform is IdentityTransform)) { IEnumerable points = GetPoints(); var bounds = BoundsHelper.GetViewportBounds(points, transform.DataTransform); Viewport2D.SetContentBounds(this, bounds); // getting new value of transform as it could change after calculating and setting content bounds. transform = GetTransform(); List transformedPoints = transform.DataToScreenAsList(points); // Analysis and filtering of unnecessary points filteredPoints = new FakePointList(FilterPoints(transformedPoints), output.Left, output.Right); if (ProvideVisiblePoints) { List viewportPointsList = new List(transformedPoints.Count); if (transform.DataTransform is IdentityTransform) { viewportPointsList.AddRange(points); } else { var viewportPoints = points.DataToViewport(transform.DataTransform); viewportPointsList.AddRange(viewportPoints); } SetVisiblePoints(this, new ReadOnlyCollection(viewportPointsList)); } Offset = new Vector(); } else { double left = output.Left; double right = output.Right; double shift = Offset.X; left -= shift; right -= shift; filteredPoints.SetXBorders(left, right); } } StreamGeometry streamGeometry = new StreamGeometry(); protected override void OnRenderCore(DrawingContext dc, RenderState state) { if (DataSource == null) return; if (filteredPoints.HasPoints) { using (StreamGeometryContext context = streamGeometry.Open()) { context.BeginFigure(filteredPoints.StartPoint, false, false); context.PolyLineTo(filteredPoints, true, smoothLinesJoin); } Brush brush = null; Pen pen = LinePen; bool isTranslated = IsTranslated; if (isTranslated) { dc.PushTransform(new TranslateTransform(Offset.X, Offset.Y)); } dc.DrawGeometry(brush, pen, streamGeometry); if (isTranslated) { dc.Pop(); } #if __DEBUG FormattedText text = new FormattedText(filteredPoints.Count.ToString(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface("Arial"), 12, Brushes.Black); dc.DrawText(text, Viewport.Output.GetCenter()); #endif } } private bool filteringEnabled = true; public bool FilteringEnabled { get { return filteringEnabled; } set { if (filteringEnabled != value) { filteringEnabled = value; filteredPoints = null; Update(); } } } private bool smoothLinesJoin = true; public bool SmoothLinesJoin { get { return smoothLinesJoin; } set { smoothLinesJoin = value; Update(); } } private List FilterPoints(List points) { if (!filteringEnabled) return points; var filteredPoints = filters.Filter(points, Viewport.Output); return filteredPoints; } } }