2D Visualization笔记(X3) DynamicDataDisplay(wpf)(一)

Hello World Demo 分析

MainWindow.xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Window x:Class="Simplest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
Title="Simplest plot sample" Height="243" Width="416">

<d3:ChartPlotter Name="plotter">
<d3:Header TextBlock.FontSize="20">
Very simple chart
</d3:Header>
<d3:VerticalAxisTitle>Sine value</d3:VerticalAxisTitle>
<d3:HorizontalAxisTitle>Sine argument</d3:HorizontalAxisTitle>
</d3:ChartPlotter>
</Window>

MainWindow.xaml.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
namespace Simplest
{
/// <summary>Interaction logic for simplest plot application</summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

Loaded += new RoutedEventHandler(MainWindow_Loaded);
}

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// Prepare data in arrays
const int N = 1000;
double[] x = new double[N];
double[] y = new double[N];

for (int i = 0; i < N; i++)
{
x[i] = i * 0.1;
y[i] = Math.Sin(x[i]);
}

// Create data sources:
var xDataSource = x.AsXDataSource();
var yDataSource = y.AsYDataSource();

CompositeDataSource compositeDataSource = xDataSource.Join(yDataSource);
// adding graph to plotter
plotter.AddLineGraph(compositeDataSource,
Colors.Goldenrod,
3,
"zmapleaf");

// Force evertyhing plotted to be visible
plotter.FitToView();
}
}
}

在Plot中插入图表

1
2
3
4
5
6
7
8
9
10
11
12
13
public static LineGraph AddLineGraph(this Plotter2D plotter, IPointDataSource pointSource, Pen linePen, Description description)
{
LineGraph graph = new LineGraph
{
DataSource = pointSource,
LinePen = linePen
};
graph.Filters.Add(new InclinationFilter());
graph.Filters.Add(new FrequencyFilter());
//graph.Filters.Add(new CountFilter());
plotter.Children.Add(graph);
return graph;
}

LineGraph的继承关系

1
2
3
4
5
6
7
8
9
10
System.Object
System.Windows.Threading.DispatcherObject
System.Windows.DependencyObject
System.Windows.Media.Visual
System.Windows.UIElement
System.Windows.FrameworkElement
Microsoft.Research.DynamicDataDisplay.PlotterElement
Microsoft.Research.DynamicDataDisplay.ViewportElement2D
Microsoft.Research.DynamicDataDisplay.PointsGraphBase
Microsoft.Research.DynamicDataDisplay.LineGraph

ChartPlotter的继承关系

1
2
3
4
5
6
7
8
9
10
11
System.Object
System.Windows.Threading.DispatcherObject
System.Windows.DependencyObject
System.Windows.Media.Visual
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Control
System.Windows.Controls.ContentControl
Microsoft.Research.DynamicDataDisplay.Plotter
Microsoft.Research.DynamicDataDisplay.Plotter2D
Microsoft.Research.DynamicDataDisplay.ChartPlotter


LineGraph如何绘制

1
filters.CollectionChanged += filters_CollectionChanged;

调用graph.Filters.Add(new InclinationFilter())之后, 触发filters_CollectionChanged事件

1
2
3
4
5
void filters_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
filteredPoints = null;
Update();
}

Update定义在父类ViewportElement2D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void Update()
{
if (Viewport == null) return;

UpdateCore();

if (!beforeFirstUpdate)
{
updateCalled = true;
InvalidateVisual();
}
beforeFirstUpdate = false;
}
protected virtual void UpdateCore() { }

LineGraph重写UpdateCore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private FakePointList filteredPoints;
protected override void UpdateCore()
{
if (DataSource == null) return;

Rect output = Viewport.Output;
var transform = GetTransform();

if (filteredPoints == null || !(transform.DataTransform is IdentityTransform))
{
IEnumerable<Point> points = GetPoints();

ContentBounds = BoundsHelper.GetViewportBounds(points, transform.DataTransform);

transform = GetTransform();
List<Point> transformedPoints = transform.DataToScreen(points);

// Analysis and filtering of unnecessary points
filteredPoints = new FakePointList(FilterPoints(transformedPoints),
output.Left, output.Right);

Offset = new Vector();
}
else
{...
}
}

UpdateCore先是将数据转换到窗口, 然后在调用Filter对数据进行过滤(裁剪)

ViewportElement2D是重点,有Update,UpdateCore(纯虚函数),OnRender(重写),OnRenderCore(纯虚函数). OnRender原本是UIElement的纯虚函数, ViewportElement2D重写OnRender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
private bool shouldReRender = true;
private DrawingGroup graphContents;
protected sealed override void OnRender(DrawingContext drawingContext)
{
if (Viewport == null) return;

Rect output = Viewport.Output;
if (output.Width == 0 || output.Height == 0) return;
if (output.IsEmpty) return;
if (Visibility != Visibility.Visible) return;

if (shouldReRender || manualTranslate || renderTarget == RenderTo.Image || beforeFirstUpdate || updateCalled)
{
if (graphContents == null)
{
graphContents = new DrawingGroup();
}
if (beforeFirstUpdate)
{
Update();
}

using (DrawingContext context = graphContents.Open())
{
if (renderTarget == RenderTo.Screen)
{
RenderState state = CreateRenderState(Viewport.Visible, RenderTo.Screen);
OnRenderCore(context, state);
}
else
{
// for future use
}
}
updateCalled = false;
}

// thumbnail is not created, if
// 1) CreateThumbnail is false
// 2) this graph has IsLayer property, set to false
if (ShouldCreateThumbnail)
{
RenderThumbnail();
}

if (!manualClip)
{
drawingContext.PushClip(new RectangleGeometry(output));
}
bool translate = !manualTranslate && IsTranslated;
if (translate)
{
drawingContext.PushTransform(new TranslateTransform(offset.X, offset.Y));
}

drawingContext.DrawDrawing(graphContents);

if (translate)
{
drawingContext.Pop();
}
if (!manualClip)
{
drawingContext.Pop();
}
shouldReRender = true;
}


protected abstract void OnRenderCore(DrawingContext dc, RenderState state);

LineGraph重写OnRenderCore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected override void OnRenderCore(DrawingContext dc, RenderState state)
{
if (DataSource == null) return;

if (filteredPoints.HasPoints)
{
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext context = geometry.Open())
{
context.BeginFigure(filteredPoints.StartPoint, false, false);
context.PolyLineTo(filteredPoints, true, true);
}
geometry.Freeze();

const Brush brush = null;
Pen pen = LinePen;

bool isTranslated = IsTranslated;
if (isTranslated)
{
dc.PushTransform(new TranslateTransform(Offset.X, Offset.Y));
}
dc.DrawGeometry(brush, pen, geometry);
if (isTranslated)
{
dc.Pop();
}
}
}

参考:
UIElement.OnRender Method (DrawingContext)
https://msdn.microsoft.com/en-us/library/system.windows.uielement.onrender(v=vs.110).aspx

DrawingContext Class
https://msdn.microsoft.com/en-us/library/system.windows.media.drawingcontext(v=vs.110).aspx

public abstract class DrawingContext : DispatcherObject, IDisposable
Describes visual content using draw, push, and pop commands.

Use a DrawingContext to populate a Visual or a Drawing with visual content.
Although the DrawingContext draw methods appear similar to the draw methods of the System.Drawing.Graphics type, they function very differently:DrawingContext is used with a retained mode graphics system, while the System.Drawing.Graphics type is used with an immediate mode graphics system. When you use a DrawingContext object’s draw commands, you are actually storing a set of rendering instructions (although the exact storage mechanism depends on the type of object that supplies the DrawingContext) that will later be used by the graphics system; you are not drawing to the screen in real-time. For more information about how the Windows Presentation Foundation (WPF) graphics system works, see WPF Graphics Rendering Overview.
You never directly instantiate a DrawingContext; you can, however, acquire a drawing context from certain methods, such as DrawingGroup.Openand DrawingVisual.RenderOpen.

StreamGeometry Class (比Shape更轻量)
https://msdn.microsoft.com/en-us/library/system.windows.media.streamgeometry(v=vs.110).aspx)
Defines a geometric shape, described using a StreamGeometryContext. This geometry is light-weight alternative to PathGeometry: it does not support data binding, animation, or modification.

Optimizing Performance: 2D Graphics and Imaging
https://msdn.microsoft.com/en-us/library/bb613591(v=vs.110).aspx
Since Shape objects derive from the FrameworkElement class, using them can add significantly more memory consumption in your application. If you really do not need the FrameworkElement features for your graphical content, consider using the lighter-weight Drawing objects.

How to: Create a Shape Using a StreamGeometry
https://msdn.microsoft.com/en-us/library/ms742199(v=vs.110).aspx