远


  • Home

  • About

  • Tags

  • Categories

  • Archives

2D Visualization笔记(X3) DynamicDataDisplay(sl)(五)

Posted on 2017-02-04 | In 编程

官方
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/dynamicdatadisplay.htm

Tutorial
http://research.microsoft.com/en-us/um/cambridge/projects/ddd/d3isdk/

D3Overview 必读
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/D3Overview.pdf

Sliverlight版本: DynamicDataDisplay.2.0.907

Dynamic Data Display 的Sliverlight版本和Wpf版本中的代码不一样.


Markers: Many markers

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
42
43
// Copyright © 2010-2011 Microsoft Corporation, All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Resources;

namespace ManyMarkersSample
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();

var y = LoadData();
var x = Enumerable.Range(0, y.Length).Select(p => (double)p).ToArray();

markers.MarkersBatchSize = 750;
markers.Plot(x, y);
}

private static double[] LoadData()
{
List<double> data = new List<double>(11000);
StreamResourceInfo sri = Application.GetResourceStream(new Uri("r0001.csv", UriKind.Relative));
using (var sr = new StreamReader(sri.Stream))
{
var s = sr.ReadLine();
while ((s = sr.ReadLine()) != null)
{
var d = double.Parse(s, CultureInfo.InvariantCulture);
data.Add(d);
}
}
return data.ToArray();
}
}
}


This sample renders 11000 markers. Note that markers appear on the screen gradually and quality of zoomed in/zoomed out image also increases gradually. This keeps the sample fully interactive even at the moments when rendering is in progress.

MarkerGraph的继承关系

1
2
3
4
5
6
7
8
9
10
System.Object
System.Windows.DependencyObject
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Panel
Microsoft.Research.DynamicDataDisplay.PlotBase
Microsoft.Research.DynamicDataDisplay.MarkerGraph
Microsoft.Research.DynamicDataDisplay.BarGraph
Microsoft.Research.DynamicDataDisplay.CartesianMarkerGraph
Microsoft.Research.DynamicDataDisplay.VerticalIntervalGraph


Rendering optimization

Silverlight has limited rendering performance and plotting more than a few thousands of elements takes signification amount of time which is enough to make application non-interactive. Scientific data sets often contain tens of thousands of data items. To overcome Silverlight limitation MarkerGraph uses bitmap caching and deferred rendering. You can see how it works in Many Markers sample in Interactive SDK. Markers appear on the screen in batches. Each new batch is rendered when application is idle, so when user navigated or interacts with UI in other manner markers rendering is suspended. When marker graph needs to be redrawn because of panning or zooming, all batches are instantly replaced with bitmaps which are translated and scaled according to user navigation. After that bitmaps are replaced with real markers batch by batch in application idle moments. You may see it clear when zooming in significantly. Markers became blurred because of bitmap scaling. Later, blurred markers are replaced with re-rendered ones which makes picture look sharp again

User can control this process using two properties. MarkerBatchSize controls size of marker batch rendered at a single pass. Larger values can cause poor application resposiveness for complex marker templates, small values will result in increase of marker graph update time.

1
2
3
4
5
6
7
8
class MarkerGraph
{
public int MarkerBatchSize { get; set; } // Dep. prop.
public int MaxSnapshotSize { get; set; } // Dep. prop.
public long Plot(params object[] data);
IObservable<RenderCompletion> RenderCompletion;
…
}

Marker graph rendering is restarted when data are changed. If data changes occur more frequently than application idle times when marker graph draws itself, most or even all of the data updates may be skipped and not shown. MarkerGraph provides technique to wait until current data is completely displayed. Plot method returns rendering task Id. When this task is either completed or skipped because new data arrives task id is observed on RenderCompletion subject. Using this technique, application can wait for markers complete draw before updating data. See ‘Background rendering’ sample in Interactive SDK

2D Visualization笔记(X3) DynamicDataDisplay(sl)(六)

Posted on 2017-02-04 | In 编程

官方
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/dynamicdatadisplay.htm

Tutorial
http://research.microsoft.com/en-us/um/cambridge/projects/ddd/d3isdk/

D3Overview 必读
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/D3Overview.pdf

Sliverlight版本: DynamicDataDisplay.2.0.907

Dynamic Data Display 的Sliverlight版本和Wpf版本中的代码不一样.


Dynamic Markers: Concurrent marker graph

xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- Copyright © 2010-2011 Microsoft Corporation, All Rights Reserved.
-->
<UserControl x:Class="ConcurrentMarkerGraphSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d3="clr-namespace:Microsoft.Research.DynamicDataDisplay;assembly=DynamicDataDisplay.Silverlight"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<d3:Chart Name="plotter">
<d3:Chart.Title>
<TextBlock HorizontalAlignment="Center" FontSize="14" Margin="0,5,0,5">Concurrent marker graph sample</TextBlock>
</d3:Chart.Title>
<d3:CircleMarkerGraph Name="markers" Description="Sine graph" Color="Red" Size="5"/>
</d3:Chart>
</Grid>
</UserControl>

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
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
// Copyright © 2010-2011 Microsoft Corporation, All Rights Reserved.

using System;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Media;

namespace ConcurrentMarkerGraphSample
{
public partial class MainPage : UserControl
{
private volatile bool isUnloaded;
private Thread thread;
private double phase;

public MainPage()
{
InitializeComponent();

markers.MarkersBatchSize = 300;

// Start computation thread when page appears on the screen
Loaded += (s, e) =>
{
thread = new Thread(ModelRun);
isUnloaded = false;
thread.Start();
};

// Stop computation thread when page is removed from the screen
Unloaded += (s, e) =>
{
isUnloaded = true;
thread.Join();
};
}

private void ModelRun()
{
const int N = 1000;
double[] y = new double[N];
for (int i = 0; i < N; i++)
y[i] = Math.Cos(i * Math.PI / 50);

while (!isUnloaded) // Run until page is on the screen
{
// Data array is updated
for (int i = 0; i < N; i++)
{
y[i] = Math.Cos(i * Math.PI / 50 + phase);
}
phase += 0.1;

// Render data only if marker graph is not renderind something.
// This reduce flickering, but not guarantee that each interation of computations
// will be rendered.
if (!markers.IsBusy)
{
// Do not forget to clone changing data before passing to plot
double[] data = (double[])y.Clone();

// MarkerGraph.Plot method is thread safe.
// Note: PlotY, PlotXY and other similar methods of derived
// classes are not thread safe and required Dispatcher.BeginInvoke
markers.Plot(null, data, Colors.Red, 5.0);
}
}
}
}
}


In this sample marker graph shows data that are computed in another thread. Important notes:

  • Although IsBusy property is used to reduce flicker, that will not guarantee that each interation will be rendered. See DynamicMarkerGraphSample for such scenario.
  • MarkerGraph.Plot method is thread safe and can be invoked without using Dispatcher.Beginlnvoke.
  • However it is unsafe to pass concurrenly updated array to Plot method. In this sample array is cloned before passing to Plot.

MarkerGraph的继承关系

1
2
3
4
5
6
7
8
9
10
System.Object
System.Windows.DependencyObject
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Panel
Microsoft.Research.DynamicDataDisplay.PlotBase
Microsoft.Research.DynamicDataDisplay.MarkerGraph
Microsoft.Research.DynamicDataDisplay.BarGraph
Microsoft.Research.DynamicDataDisplay.CartesianMarkerGraph
Microsoft.Research.DynamicDataDisplay.VerticalIntervalGraph

2D Visualization笔记(X3) DynamicDataDisplay(sl)(四)

Posted on 2017-02-04 | In 编程

官方
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/dynamicdatadisplay.htm

Tutorial
http://research.microsoft.com/en-us/um/cambridge/projects/ddd/d3isdk/

D3Overview 必读
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/D3Overview.pdf

Sliverlight版本: DynamicDataDisplay.2.0.907

Dynamic Data Display 的Sliverlight版本和Wpf版本中的代码不一样.


BackgroundBitmapRenderer

前两个笔记分别讲了Dynamic Heatmap的两种实现方式, 这里看看Heatmap的基类BackgroundBitmapRenderer

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Subjects;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>
/// Base class for renderers, which can prepare its render results in a separate thread.
/// </summary>
public abstract class BackgroundBitmapRenderer : Plot
{
private delegate void RenderFunc(RenderResult r, RenderTaskState state);

private Image outputImage;

/// <summary>Cartesian coordinates of image currently on the screen.</summary>
private DataRect imageCartesianRect;

/// <summary>Size of image currently on the screen.</summary>
private Size imageSize;

private int maxTasks;

private Queue<long> tasks = new Queue<long>();

private List<RenderTaskState> runningTasks;

private long nextID;

private Size prevSize = new Size(double.NaN, double.NaN);

private double prevScaleX = double.NaN;

private double prevScaleY = double.NaN;

private double prevOffsetX = double.NaN;

private double prevOffsetY = double.NaN;

private Subject<RenderCompletion> renderCompletion = new Subject<RenderCompletion>();

/// <summary>
/// Gets event which is occured when render task is finished
/// </summary>
public IObservable<RenderCompletion> RenderCompletion
{
get
{
return this.renderCompletion;
}
}

/// <summary>
/// Initializes new instance of <see cref="T:Microsoft.Research.DynamicDataDisplay.BackgroundBitmapRenderer" /> class, performing all basic preparings for inheriting classes.
/// </summary>
protected BackgroundBitmapRenderer()
{
this.maxTasks = Math.Max(1, Environment.get_ProcessorCount() - 1);
this.runningTasks = new List<RenderTaskState>();
this.outputImage = new Image();
this.outputImage.set_Stretch(0);
this.outputImage.set_VerticalAlignment(1);
this.outputImage.set_HorizontalAlignment(1);
base.get_Children().Add(this.outputImage);
base.add_Unloaded(new RoutedEventHandler(this.BackgroundBitmapRendererUnloaded));
}

private void BackgroundBitmapRendererUnloaded(object sender, RoutedEventArgs e)
{
this.CancelAll();
}

/// <summary>
/// Measures the size in layout required for child elements and determines a size for parent.
/// </summary>
/// <param name="availableSize">The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param>
/// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
protected override Size MeasureOverride(Size availableSize)
{
availableSize = base.MeasureOverride(availableSize);
this.outputImage.Measure(availableSize);
if (this.prevSize != availableSize || this.prevOffsetX != this.masterField.OffsetX || this.prevOffsetY != this.masterField.OffsetY || this.prevScaleX != this.masterField.ScaleX || this.prevScaleY != this.masterField.ScaleY)
{
this.prevSize = availableSize;
this.prevOffsetX = this.masterField.OffsetX;
this.prevOffsetY = this.masterField.OffsetY;
this.prevScaleX = this.masterField.ScaleX;
this.prevScaleY = this.masterField.ScaleY;
this.CancelAll();
if (this.imageSize.get_Width() > 0.0 && this.imageSize.get_Height() > 0.0)
{
Point point = new Point(base.LeftFromX(this.imageCartesianRect.XMin), base.TopFromY(this.imageCartesianRect.YMax));
Point point2 = new Point(base.LeftFromX(this.imageCartesianRect.XMax), base.TopFromY(this.imageCartesianRect.YMin));
Canvas.SetLeft(this.outputImage, point.get_X());
Canvas.SetTop(this.outputImage, point.get_Y());
UIElement arg_1BA_0 = this.outputImage;
ScaleTransform scaleTransform = new ScaleTransform();
scaleTransform.set_ScaleX((point2.get_X() - point.get_X()) / this.imageSize.get_Width());
scaleTransform.set_ScaleY((point2.get_Y() - point.get_Y()) / this.imageSize.get_Height());
arg_1BA_0.set_RenderTransform(scaleTransform);
}
this.QueueRenderTask();
}
return availableSize;
}

private void EnqueueTask(long id)
{
if (this.runningTasks.get_Count() >= this.maxTasks)
{
this.tasks.Enqueue(id);
return;
}
Size screenSize = new Size(Math.Abs(base.LeftFromX(base.ActualPlotRect.XMax) - base.LeftFromX(base.ActualPlotRect.XMin)), Math.Abs(base.TopFromY(base.ActualPlotRect.YMax) - base.TopFromY(base.ActualPlotRect.YMin)));
RenderTaskState renderTaskState = new RenderTaskState(base.ActualPlotRect, screenSize);
renderTaskState.Id = id;
renderTaskState.Bounds = this.ComputeBounds();
this.runningTasks.Add(renderTaskState);
if (!DesignerProperties.get_IsInDesignTool())
{
ThreadPool.QueueUserWorkItem(delegate(object s)
{
RenderResult renderResult = this.RenderFrame((RenderTaskState)s);
base.get_Dispatcher().BeginInvoke(new BackgroundBitmapRenderer.RenderFunc(this.OnTaskCompleted), new object[]
{
renderResult,
s
});
}, renderTaskState);
return;
}
RenderResult r = this.RenderFrame(renderTaskState);
this.OnTaskCompleted(r, renderTaskState);
}

/// <summary>
/// Renders frame and returns it as a render result.
/// </summary>
/// <param name="state">Render task state for rendering frame.</param>
/// <returns>Render result of rendered frame.</returns>
protected virtual RenderResult RenderFrame(RenderTaskState state)
{
return null;
}

/// <summary>Creates new render task and puts it to queue.</summary>
/// <returns>Async operation ID.</returns>
protected long QueueRenderTask()
{
long num;
this.nextID = (num = this.nextID) + 1L;
long num2 = num;
this.EnqueueTask(num2);
return num2;
}

private void OnTaskCompleted(RenderResult r, RenderTaskState state)
{
if (r != null && !state.IsCanceled)
{
WriteableBitmap writeableBitmap = new WriteableBitmap((int)r.Output.get_Width(), (int)r.Output.get_Height());
Array.Copy(r.Image, writeableBitmap.get_Pixels(), writeableBitmap.get_PixelHeight() * writeableBitmap.get_PixelWidth());
writeableBitmap.Invalidate();
this.outputImage.set_Source(writeableBitmap);
Canvas.SetLeft(this.outputImage, r.Output.get_Left());
Canvas.SetTop(this.outputImage, r.Output.get_Top());
this.imageCartesianRect = r.Visible;
this.imageSize = new Size(r.Output.get_Width(), r.Output.get_Height());
this.outputImage.set_RenderTransform(null);
}
this.RaiseTaskCompletion(state.Id);
this.runningTasks.Remove(state);
while (this.tasks.get_Count() > 1)
{
long id = this.tasks.Dequeue();
this.RaiseTaskCompletion(id);
}
if (this.tasks.get_Count() > 0 && this.runningTasks.get_Count() < this.maxTasks)
{
this.EnqueueTask(this.tasks.Dequeue());
}
base.InvalidateMeasure();
}

/// <summary>
/// Cancel all tasks, which are in quere.
/// </summary>
public void CancelAll()
{
using (List<RenderTaskState>.Enumerator enumerator = this.runningTasks.GetEnumerator())
{
while (enumerator.MoveNext())
{
RenderTaskState current = enumerator.get_Current();
current.Stop();
}
}
}

/// <summary>
/// Raises RenderCompletion event when task with the specified id is finished
/// </summary>
/// <param name="id">ID of finished task</param>
protected void RaiseTaskCompletion(long id)
{
this.renderCompletion.OnNext(new RenderCompletion
{
TaskId = id
});
}
}
}


关键在于理解OnTaskCompleted中的这一段

1
2
3
4
5
6
7
8
9
while (this.tasks.get_Count() > 1)
{
long id = this.tasks.Dequeue();
this.RaiseTaskCompletion(id);
}
if (this.tasks.get_Count() > 0 && this.runningTasks.get_Count() < this.maxTasks)
{
this.EnqueueTask(this.tasks.Dequeue());
}

完成当前task后,

  1. 将tasks中的其他taskId去掉, 只剩下1个;//while循环
  2. 将剩下的1个taskId作为参数, 调用EnqueueTask;//if语句

剩1个, 双核的话, maxTasks为1

1
this.maxTasks = Math.Max(1, Environment.get_ProcessorCount() - 1);

举个例子: tasks中有taskId: 1, 2, 3, 4
1当前完成, tasks去掉2和3, 剩下4, 然后将4作为参数, 调用EnqueueTask

1和4是调用EnqueueTask, 真正绘制的; 2和3虽然调用了Plot方法, 但没有绘制

这就是work thread和render thread没有同步的结果. 1还没绘制好, 2,3,4的数据已经来了.
这是「DynamicDataDisplay(sl)笔记(二)」中会出现的状况.

一个解决的方式是work thread等到render thread绘制完之后, 才准备下一个render的数据; 即等1绘制完之后, 才让work thread
准备2的数据.也就是说work thread和render thread之间要进行消息通信. 信息通信依靠AutoResetEvent.

问题来了, work thread怎么知道render thread绘制完了. 看Plot的代码, Plot只是返回了一个taskId; 多次调用Plot会产生
多个taskId, 这些taskId构成一个序列.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public long Plot(double[,] data, double[] x, double[] y, double missingValue)
{
HeatmapGraph.VerifyDimensions(data, x, y);
lock (this.locker)
{
this.xArr = x;
this.yArr = y;
this.data = data;
this.missingValue = missingValue;
this.dataVersion += 1L;
}
this.InvalidateBounds();
return base.QueueRenderTask();
}

作者这里用reactive extension的Subject(Subject既是IObservable也是IObserver)对序列进行过滤
当完成的RenderCompletion中的taskId 等于 Plot返回的id, 表示当前Plot绘制完成, 转而通知work thread继续生成新的数据
然后进行下一次Plot; 这样tasks中不会累积1,2,3,4

1
2
3
4
5
6
7
8
9
10
11
12
13
private void PlotData()
{
// HeatmapGraph objects prepare images to be drawn in a background thread.
// The Plot method cancels current incomplete images before starting a new one.
// This increases responsiveness of the UI but may result in loss of certain
// or even all of the frames.
// The following code shows how to wait until a certain data is actually drawn.

long id = heatmap.Plot(f, x, y); // receive a unique operation identifier
heatmap.RenderCompletion // an observable of completed and cancelled operations
.Where(rc => rc.TaskId == id) // filter out an operation with the known id
.Subscribe(dummy => { renderComplete.Set(); }); // signal when the id is observed
}

前一种方式和后一种方式都值得学习

2D Visualization笔记(X3) DynamicDataDisplay(sl)(三)

Posted on 2017-02-04 | In 编程

官方
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/dynamicdatadisplay.htm

Tutorial
http://research.microsoft.com/en-us/um/cambridge/projects/ddd/d3isdk/

D3Overview 必读
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/D3Overview.pdf

Sliverlight版本: DynamicDataDisplay.2.0.907

Dynamic Data Display 的Sliverlight版本和Wpf版本中的代码不一样.


Heatmap: background computations

xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- Copyright © 2010-2011 Microsoft Corporation, All Rights Reserved.
-->
<UserControl x:Class="DynamicHeatmapSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:d3="clr-namespace:Microsoft.Research.DynamicDataDisplay;assembly=DynamicDataDisplay.Silverlight">

<Grid x:Name="LayoutRoot" Background="White">
<d3:Chart>
<d3:Chart.Title>
<TextBlock FontSize="14" Margin="0,5,0,5">Background computations sample</TextBlock>
</d3:Chart.Title>
<d3:HeatmapGraph x:Name="heatmap" Palette="Yellow,Blue,Orange"/>
</d3:Chart>
</Grid>

</UserControl>

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
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Copyright © 2010-2011 Microsoft Corporation, All Rights Reserved.

using System;
using System.Reactive.Linq;
using System.Threading;
using System.Windows.Controls;

namespace DynamicHeatmapSample
{
public partial class MainPage : UserControl
{
private volatile bool isUnloaded = false;
private AutoResetEvent renderComplete = new AutoResetEvent(false);
private Thread thread;
private double phase = 0;

const int N = 1000;
const int M = 500;

double[] x = new double[N + 1];
double[] y = new double[M + 1];
double[,] f = new double[N, M];

public MainPage()
{
InitializeComponent();

// Start computation thread when Page appears on the screen
Loaded += (s, e) =>
{
thread = new Thread(ModelRun);
isUnloaded = false;
thread.Start();
};

// Stop computation thread when Page is removed from the screen
Unloaded += (s, e) =>
{
isUnloaded = true;
renderComplete.Set();
thread.Join();
};
}

private void ModelRun()
{
// Coordinate grid is constant
for (int i = 0; i <= N; i++)
x[i] = -Math.PI + 2 * i * Math.PI / N;

for (int j = 0; j <= M; j++)
y[j] = -Math.PI / 2 + j * Math.PI / M;

while (!isUnloaded) // Run until page is on the screen
{
// Data array is updated
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
f[i, j] = Math.Sqrt(x[i] * x[i] + y[j] * y[j]) * Math.Abs(Math.Cos(x[i] * x[i] + y[j] * y[j] + phase));
phase += 0.1;

// Uncomment next line to simulate delay in computations
// Thread.Sleep(1000);

// Plot the computed array.
// The Plot method must be called from the UI dispatcher thread.
// Unlike Tutoral3 sample here we wait for each frame to be rendered.
if (!isUnloaded)
{
renderComplete.Reset();
Dispatcher.BeginInvoke(PlotData);
renderComplete.WaitOne();
}
}
}

private void PlotData()
{
// HeatmapGraph objects prepare images to be drawn in a background thread.
// The Plot method cancels current incomplete images before starting a new one.
// This increases responsiveness of the UI but may result in loss of certain
// or even all of the frames.
// The following code shows how to wait until a certain data is actually drawn.

long id = heatmap.Plot(f, x, y); // receive a unique operation identifier
heatmap.RenderCompletion // an observable of completed and cancelled operations
.Where(rc => rc.TaskId == id) // filter out an operation with the known id
.Subscribe(dummy => { renderComplete.Set(); }); // signal when the id is observed
}
}
}


This sample shows how to plot data that are computed in separated thread. Unlike Tutorial 3, in this sample computational thread waits for each frame to be rendered. This is achieved by observing RenderCompletion event and signaling when render operation with specific ID is completed (see ‘PlotData’ method for details).

2D Visualization笔记(X3) DynamicDataDisplay(sl)(二)

Posted on 2017-02-04 | In 编程

官方
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/dynamicdatadisplay.htm

Tutorial
http://research.microsoft.com/en-us/um/cambridge/projects/ddd/d3isdk/

D3Overview 必读
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/D3Overview.pdf

Sliverlight版本: DynamicDataDisplay.2.0.907

Dynamic Data Display 的Sliverlight版本和Wpf版本中的代码不一样.


Tutorial 3: Drawing dynamic heatmap

xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- Copyright © 2010-2011 Microsoft Corporation, All Rights Reserved.
-->
<UserControl x:Class="Tutorial3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d3="clr-namespace:Microsoft.Research.DynamicDataDisplay;assembly=DynamicDataDisplay.Silverlight"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<d3:Chart>
<d3:Chart.Title>
<TextBlock HorizontalAlignment="Center" FontSize="14" Margin="0,5,0,5">Tutorial 3: Dynamic heatmap</TextBlock>
</d3:Chart.Title>
<d3:HeatmapGraph Name="heatmap" Palette="Blue,#00FF00,Red"/>
</d3:Chart>
</Grid>
</UserControl>

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
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
71
72
73
74
75
76
77
78
79
80
// Copyright © 2010-2011 Microsoft Corporation, All Rights Reserved.

using System;
using System.Threading;
using System.Windows.Controls;

namespace Tutorial3
{
public partial class MainPage : UserControl
{
private volatile bool isUnloaded = false;
private Thread thread;
private double phase = 0;

public MainPage()
{
InitializeComponent();

// Start computation thread when page appears on the screen
Loaded += (s, e) =>
{
thread = new Thread(ModelRun);
isUnloaded = false;
thread.Start();
};

// Stop computation thread when page is removed from the screen
Unloaded += (s, e) =>
{
isUnloaded = true;
thread.Join();
};
}

/// <summary>Performs computations. Runs until isUnloaded became true.</summary>
private void ModelRun()
{
const int N = 1000;
const int M = 500;

// 1D arrays for grid
double[] x = new double[N + 1];
double[] y = new double[M + 1];

// Size of data array here is one less than size of the grid.
// HeatmapGraph will render in bitmap mode, when grid of size (N+1) x (M+1)
// defines N x M rectangular cells filled with solid color according to
// corresponding element of data array.
double[,] f = new double[N, M];

// Coordinate grid is constant and it is initialized once
for (int i = 0; i <= N; i++)
x[i] = -Math.PI + 2 * i * Math.PI / N;

for (int j = 0; j <= M; j++)
y[j] = -Math.PI / 2 + j * Math.PI / M;

while (!isUnloaded) // Run until page is on the screen
{
// Compute next iteration and store it in data array
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
f[i, j] = Math.Sqrt(x[i] * x[i] + y[j] * y[j])
* Math.Abs(Math.Cos(x[i] * x[i] + y[j] * y[j] + phase));
phase += 0.1;

// Uncomment next line to simulate delay in computations
// Thread.Sleep(1000);

// Plot the computed array.
//
// The Plot method must be called from the UI dispatcher thread.
// We pass a clone of the data array to the Plot method to prevent
// concurrent modification of the plotted data.
Dispatcher.BeginInvoke(
new Action<object>(data => heatmap.Plot((double[,])data, x, y)), f.Clone());
}
}
}
}


很常规的做法: 起一个线程用来做计算, 但Plot的调用必须在UI线程中, 因为heatmap是UI线程创建的; 注释已经写的比较明白了, 但在Discription中提到如下:

Please note that you need to use BeginInvoke to access UI elements from other threads and you need to clone all arrays that are changed in computation thread
here Plot method acts as asyncronous. It returns immediately and picture appears on the screen when it is completed. More complex approach when computation thread is waiting for each frame to be shown on the screen is present in code of tutorial

HeatmapGraph

1
2
3
4
5
6
7
8
9
System.Object
System.Windows.DependencyObject
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Panel
Microsoft.Research.DynamicDataDisplay.PlotBase
Microsoft.Research.DynamicDataDisplay.Plot
Microsoft.Research.DynamicDataDisplay.BackgroundBitmapRenderer
Microsoft.Research.DynamicDataDisplay.HeatmapGraph

Plot方法是异步的, 看看Plot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public long Plot(double[,] data, double[] x, double[] y, double missingValue)
{
HeatmapGraph.VerifyDimensions(data, x, y);
lock (this.locker)
{
this.xArr = x;
this.yArr = y;
this.data = data;
this.missingValue = missingValue;
this.dataVersion += 1L;
}
this.InvalidateBounds();
return base.QueueRenderTask();
}

base是指BackgroundBitmapRenderer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// Base class for renderers, which can prepare its render results in a separate thread.
/// </summary>
public abstract class BackgroundBitmapRenderer : Plot
{
// Microsoft.Research.DynamicDataDisplay.BackgroundBitmapRenderer
/// <summary>Creates new render task and puts it to queue.</summary>
/// <returns>Async operation ID.</returns>
protected long QueueRenderTask()
{
long num;
this.nextID = (num = this.nextID) + 1L;
long num2 = num;
this.EnqueueTask(num2);
return num2;
}
}

关于BackgroundBitmapRenderer需要单独写一篇, 先暂且到这

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

Posted on 2017-02-04 | In 编程

官方
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/dynamicdatadisplay.htm

Tutorial
http://research.microsoft.com/en-us/um/cambridge/projects/ddd/d3isdk/

D3Overview 必读
http://research.microsoft.com/en-us/um/cambridge/groups/science/tools/d3/D3Overview.pdf

Sliverlight版本: DynamicDataDisplay.2.0.907

Dynamic Data Display 的Sliverlight版本和Wpf版本中的代码不一样.


Tutorial 1: Line graph

xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- Copyright © 2010-2011 Microsoft Corporation, All Rights Reserved.
-->
<UserControl x:Class="Tutorial1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d3="clr-namespace:Microsoft.Research.DynamicDataDisplay;assembly=DynamicDataDisplay.Silverlight"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<!-- D3 markup starts here-->
<d3:Chart BottomTitle="Sinc argument" LeftTitle="Sinc value">
<d3:Chart.Title>
<TextBlock HorizontalAlignment="Center" FontSize="14" Margin="0,5,0,5">Tutorial 1: Line graph</TextBlock>
</d3:Chart.Title>
<d3:LineGraph x:Name="linegraph" Description="Sinc graph" Stroke="Blue" StrokeThickness="3"/>
</d3:Chart>
</Grid>
</UserControl>

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
// Copyright © 2010-2011 Microsoft Corporation, All Rights Reserved.

using System;
using System.Linq;
using System.Windows.Controls;

namespace Tutorial1
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();

// Compute x array of 1001 points from 0 to 100 with 0.1 step
var x = Enumerable.Range(0, 1001).Select(i => i / 10.0).ToArray();

// Compute y array as sin(x)/x function defined on x grid
var y = x.Select(v => Math.Abs(v) < 1e-10 ? 1 : Math.Sin(v) / v).ToArray();

// Plot data
linegraph.Plot(x, y);
}
}
}


Chart

1
2
3
4
5
6
7
System.Object
System.Windows.DependencyObject
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Control
System.Windows.Controls.ContentControl
Microsoft.Research.DynamicDataDisplay.Chart

LineGraph

1
2
3
4
5
6
7
8
System..Object
System.Windows.DependencyObject
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Panel
Microsoft.Research.DynamicDataDisplay.PlotBase
Microsoft.Research.DynamicDataDisplay.Plot
Microsoft.Research.DynamicDataDisplay.LineGraph

Line graph uses internally Polyline Silverlight element with attached Plot.Points property. Line graph adds Plot methods, Description
property and legend support to existing Polyline functionality; 是在Polyline(SL内置)的基础上添加了Plot方法, Description(描述)和legend(图例)
——D3Overview

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
public class LineGraph : Plot 
{
...
private Polyline polyline;

/// <summary>
/// Identifies <see cref="P:Microsoft.Research.DynamicDataDisplay.LineGraph.Points" /> dependecy property
/// </summary>
public new static readonly DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(PointCollection), typeof(LineGraph), new PropertyMetadata(new PointCollection(), delegate(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
LineGraph lineGraph = (LineGraph)o;
if (lineGraph != null)
{
Microsoft.Research.DynamicDataDisplay.Plot.SetPoints(lineGraph.polyline, (PointCollection)e.get_NewValue());
}
}));

public void Plot(IEnumerable x, IEnumerable y)
{
...
PointCollection pointCollection = new PointCollection();
IEnumerator enumerator = x.GetEnumerator();
IEnumerator enumerator2 = y.GetEnumerator();
...
this.Points = pointCollection;
}

/// <summary>
/// Gets or sets line graph points.
/// </summary>
public PointCollection Points
{
get
{
return (PointCollection)base.GetValue(LineGraph.PointsProperty);
}
set
{
base.SetValue(LineGraph.PointsProperty, value);
}
}


}


变换

与wpf版本中类似
Each plot requires a coordinate transform, actually a composition of data transform and plot transform.

Data transform for a given data element d computes its vertical or horizontal coordinate x or y on plot plane

Plot transform is a transform from plot coordinates to screen coordinates. Plot transform is
always a composition of Translate and Scale transforms, so it is independent along each axis and defined by
four numbers xscale, yscale, xoffset, yoffset

PlotBase是Plot最重要的类
PlotBase is a Silverlight panel that facilitates several tasks:

  • Coordinate transformation between screen and user data
  • AutoFit mode support
  • Management of plot composition and coordinate transforms synchronization

Plot, Figure和Chart

  • LineGraph是Plot
  • Figure class is a panel derived from PlotBase and provides special layout options that are often found in
    charts. It provides attached property Placement that allows to place child elements in center, left, top, right
    and bottom slots
  • Chart element is a prepackaged(预包装) figure with axis, grid lines, legend and title

Figure 更灵活, Chart更方便

1
2
3
4
5
6
7
8
<Grid x:Name="LayoutRoot" Background="White">
<d3:Figure PlotOriginY="-1.5" PlotHeight="3" PlotOriginX="{Binding Value, ElementName=slider, Mode=TwoWay}" PlotWidth="30" IsAutoFitEnabled="False">
<TextBlock d3:Figure.Placement="Top" Text="Axis with ScrollBar sample" TextAlignment="Center" FontSize="14" Margin="5"/>
<d3:LineGraph x:Name="cos" Grid.Row="1"/>
<Slider x:Name="slider" Maximum="100" d3:Figure.Placement="Bottom"/>
<d3:PlotAxis d3:Figure.Placement="Bottom"/>
</d3:Figure>
</Grid>

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

Posted on 2017-02-03 | In 编程

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

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

Posted on 2017-02-03 | In 编程

Hello World Demo 分析
LineGraph的UpdateCore中有GetTransform和transform.DataToScreen(points). GetTransform是在父类PointsGraphBase中定义的

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
private DataTransform dataTransform = null;
public DataTransform DataTransform
{
get { return dataTransform; }
set
{
if (dataTransform != value)
{
dataTransform = value;
Update();
}
}
}

protected CoordinateTransform GetTransform()
{
if (Plotter == null)
return null;

var transform = Plotter2D.Viewport.Transform;
if (dataTransform != null)
transform = transform.WithDataTransform(dataTransform);

return transform;
}

一个窗口中可以有多个Plot, 一个Plot就是一个视口(viewPort);CoordinateTransform有2个变换组成, 一个是数据变换(dataTransform), 先将数据变换到Plot(即视口viewPort), 另一个是视口变换, 将Plot变换到窗口的某一位置

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
	/// <summary>
/// Base class for all data transforms.
/// Defines methods to transform point from data coordinate system to viewport coordinates and vice versa.
/// </summary>
public abstract class DataTransform
{
/// <summary>
/// Transforms the point in data coordinates to viewport coordinates.
/// </summary>
/// <param name="pt">The point in data coordinates.</param>
/// <returns></returns>
public abstract Point DataToViewport(Point pt);
/// <summary>
/// Transforms the point in viewport coordinates to data coordinates.
/// </summary>
/// <param name="pt">The point in viewport coordinates.</param>
/// <returns></returns>
public abstract Point ViewportToData(Point pt);

private static readonly DataRect defaultDomain = DataRect.Empty;
/// <summary>
/// Gets the data domain.
/// </summary>
/// <value>The data domain.</value>
public virtual DataRect DataDomain { get { return defaultDomain; } }
}


namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>
/// A central class in 2d coordinate transformation in DynamicDataDisplay.
/// Provides methods to transform point from one coordinate system to another.
/// </summary>
public sealed class CoordinateTransform
{
private CoordinateTransform(Rect visibleRect, Rect screenRect)
{
this.visibleRect = visibleRect;
this.screenRect = screenRect;

rxToScreen = screenRect.Width / visibleRect.Width;
ryToScreen = screenRect.Height / visibleRect.Height;
cxToScreen = visibleRect.Left * rxToScreen - screenRect.Left;
cyToScreen = screenRect.Height + screenRect.Top + visibleRect.Top * ryToScreen;

rxToData = visibleRect.Width / screenRect.Width;
ryToData = visibleRect.Height / screenRect.Height;
cxToData = screenRect.Left * rxToData - visibleRect.Left;
cyToData = visibleRect.Height + visibleRect.Top + screenRect.Top * ryToData;
}

#region Coeffs
double rxToScreen;
double ryToScreen;
double cxToScreen;
double cyToScreen;

double rxToData;
double ryToData;
double cxToData;
double cyToData;
#endregion

#region Creation methods

internal CoordinateTransform WithRects(Rect visibleRect, Rect screenRect)
{
CoordinateTransform copy = new CoordinateTransform(visibleRect, screenRect);
copy.dataTransform = dataTransform;
return copy;
}

/// <summary>
/// Creates a new instance of CoordinateTransform with the given data transform.
/// </summary>
/// <param name="dataTransform">The data transform.</param>
/// <returns></returns>
public CoordinateTransform WithDataTransform(DataTransform dataTransform)
{
if (dataTransform == null)
throw new ArgumentNullException("dataTransform");

CoordinateTransform copy = new CoordinateTransform(visibleRect, screenRect);
copy.dataTransform = dataTransform;
return copy;
}

internal static CoordinateTransform CreateDefault()
{
CoordinateTransform transform = new CoordinateTransform(new Rect(0, 0, 1, 1), new Rect(0, 0, 1, 1));

return transform;
}

#endregion

#region Transform methods

/// <summary>
/// Transforms point from data coordinates to screen.
/// </summary>
/// <param name="dataPoint">The point in data coordinates.</param>
/// <returns></returns>
public Point DataToScreen(Point dataPoint)
{
Point viewportPoint = dataTransform.DataToViewport(dataPoint);

Point screenPoint = new Point(viewportPoint.X * rxToScreen - cxToScreen,
cyToScreen - viewportPoint.Y * ryToScreen);

return screenPoint;
}

/// <summary>
/// Transforms point from screen coordinates to data coordinates.
/// </summary>
/// <param name="screenPoint">The point in screen coordinates.</param>
/// <returns></returns>
public Point ScreenToData(Point screenPoint)
{
Point viewportPoint = new Point(screenPoint.X * rxToData - cxToData,
cyToData - screenPoint.Y * ryToData);

Point dataPoint = dataTransform.ViewportToData(viewportPoint);

return dataPoint;
}

/// <summary>
/// Transforms point from viewport coordinates to screen coordinates.
/// </summary>
/// <param name="viewportPoint">The point in viewport coordinates.</param>
/// <returns></returns>
public Point ViewportToScreen(Point viewportPoint)
{
Point screenPoint = new Point(viewportPoint.X * rxToScreen - cxToScreen,
cyToScreen - viewportPoint.Y * ryToScreen);

return screenPoint;
}

/// <summary>
/// Transforms point from screen coordinates to viewport coordinates.
/// </summary>
/// <param name="screenPoint">The point in screen coordinates.</param>
/// <returns></returns>
public Point ScreenToViewport(Point screenPoint)
{
Point viewportPoint = new Point(screenPoint.X * rxToData - cxToData,
cyToData - screenPoint.Y * ryToData);

return viewportPoint;
}

#endregion

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private Rect visibleRect;
/// <summary>
/// Gets the viewport rectangle.
/// </summary>
/// <value>The viewport rect.</value>
public Rect ViewportRect
{
get { return visibleRect; }
}

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private Rect screenRect;
/// <summary>
/// Gets the screen rectangle.
/// </summary>
/// <value>The screen rect.</value>
public Rect ScreenRect
{
get { return screenRect; }
}

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private DataTransform dataTransform = DataTransforms.Identity;
/// <summary>
/// Gets the data transform.
/// </summary>
/// <value>The data transform.</value>
public DataTransform DataTransform
{
get { return dataTransform; }
}
}
}

visibleRect是Plot所在的Rect, screenRect是窗口所在的Rect, 默认两个是重合的

1
2
3
4
5
6
internal static CoordinateTransform CreateDefault()
{
CoordinateTransform transform = new CoordinateTransform(new Rect(0, 0, 1, 1), new Rect(0, 0, 1, 1));

return transform;
}

2D Visualization笔记(X1) SciChart(一)

Posted on 2017-01-23 | In 编程

SciChart(3.1.4549版本)

目录

  • BindToDataSeriesSetView.xaml
  • NumericAxis
  • FastLineRenderableSeries
  • SciChartSurface
  • HighSpeedRenderSurface
  • ViewConverter
  • IRenderContext2D
  • IRenderPassData

摘要

  1. BindToDataSeriesSetView.xaml由NumericAxis(坐标轴), FastLineRenderableSeries(曲线), SciChartSurface(Chart区域)构成;
  2. SciChartSurface的DoDrawingLoop用到IRenderSurface2D(HighSpeedRenderSurfaceu实现, 用来获取IRenderContext2D, 类似Winform中的Graphics)和ISciChartRenderer(ViewConverter实现, 以IRenderContext2D为参数进行绘制);
  3. ViewConverter的RenderLoop中会调用NumericAxis和FastLineRenderableSeries的OnDraw方法, 而IRenderContext2D和IRenderPassData是OnDraw的两个参数

BindToDataSeriesSetView.xaml

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
<UserControl x:Class="Abt.Controls.SciChart.Example.Examples.IWantTo.UseSciChartWithMvvm.BindToDataSeriesSetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:SciChart="http://schemas.abtsoftware.co.uk/scichart"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<!-- Button is used to invoke a delegate in the ViewModel to append 50 points. -->
<Button Grid.Row="0" Command="{Binding AppendDataCommand}" Content="Append 50 Points"/>

<!-- Declare a SciChartSurface and bind to data -->
<SciChart:SciChartSurface Grid.Row="1" SciChart:ThemeManager.Theme="ExpressionLight">

<SciChart:SciChartSurface.RenderableSeries>
<SciChart:FastLineRenderableSeries DataSeries="{Binding ChartData}" SeriesColor="#279B27"/>
</SciChart:SciChartSurface.RenderableSeries>

<SciChart:SciChartSurface.XAxis>
<SciChart:NumericAxis AutoRange="Always"/>
</SciChart:SciChartSurface.XAxis>

<SciChart:SciChartSurface.YAxis>
<SciChart:NumericAxis AutoRange="Always" GrowBy="0.1, 0.1"/>
</SciChart:SciChartSurface.YAxis>

</SciChart:SciChartSurface>
</Grid>
</UserControl>

程序仅绘制了一条曲线和XY坐标轴

工具: ILSpy
dll: Abt.Controls.SciChart.Wpf.dll(3.1.4549版本)

程序中是
绘制1个FastLineRenderableSeries
绘制2个NumericAxis


先看NumericAxis

NumericAxis

public class NumericAxis : AxisBase
public abstract class AxisBase : ContentControl, INotifyPropertyChanged, IXmlSerializable, IDrawable, IAxisParams, IHitTestable, ISuspendable, IInvalidatableElement, IAxis

看IDrawable

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
namespace Abt.Controls.SciChart.Visuals
{
/// <summary>
/// Defines the base interface for an object that can be drawn
/// </summary>
public interface IDrawable
{
/// <summary>
/// Gets or sets the width of the <see cref="T:Abt.Controls.SciChart.Visuals.IDrawable" /> in pixels
/// </summary>
double Width
{
get;
set;
}

/// <summary>
/// Gets or sets the height of the <see cref="T:Abt.Controls.SciChart.Visuals.IDrawable" /> in pixels
/// </summary>
double Height
{
get;
set;
}

/// <summary>
/// Called when the instance is drawn
/// </summary>
/// <param name="renderContext">The <see cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" /> used for drawing</param>
/// <param name="renderPassData">Contains arguments and parameters for this render pass</param>
void OnDraw(IRenderContext2D renderContext, IRenderPassData renderPassData);
}
}

回看AxisBase类中有OnDraw方法, 有计算ticks, 绘制坐标网格, 绘制坐标轴; NumericAxis使用OnDraw进行绘制

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
/// <summary>
/// Called when the instance is drawn
/// </summary>
/// <param name="renderContext">The <see cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" /> used for drawing</param>
/// <param name="renderPassData">Contains arguments and parameters for this render pass</param>
/// <seealso cref="T:Abt.Controls.SciChart.Visuals.IDrawable" />
/// <seealso cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" />
public void OnDraw(IRenderContext2D renderContext, IRenderPassData renderPassData)
{
if (!this.IsSuspended && this.DisconnectSelection() && this.IsLicenseValid)
{
using (IUpdateSuspender updateSuspender = this.SuspendUpdates())
{
updateSuspender.ResumeTargetOnDispose = false;
Stopwatch stopwatch = Stopwatch.StartNew();
if (this.LabelProvider != null)
{
this.LabelProvider.OnBeginAxisDraw();
}
this.uriInstance = this.CalculateTicks();
if (this.IsPrimaryAxis)
{
this.DrawGridLines(renderContext, this.uriInstance);
}
if (base.Visibility == Visibility.Visible && this.ConnectContext())
{
this.OnDrawAxis(this.uriInstance);
}
stopwatch.Stop();
SciChartDebugLogger.Instance.WriteLine("Drawn {0}: Width={1}, Height={2} in {3}ms", new object[]
{
base.GetType().Name,
base.ActualWidth,
base.ActualHeight,
stopwatch.ElapsedMilliseconds
});
}
return;
}
}

再看FastLineRenderableSeries

FastLineRenderableSeries

public class FastLineRenderableSeries : BaseRenderableSeries
FastLineRenderableSeries中发现方法InternalDraw

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
/// <summary>
/// Draws the series using the <see cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" /> and the <see cref="T:Abt.Controls.SciChart.Visuals.RenderableSeries.IRenderPassData" /> passed in
/// </summary>
/// <param name="renderContext">The render context. This is a graphics object which has methods to draw lines, quads and polygons to the screen</param>
/// <param name="renderPassData">The render pass data. Contains a resampled <see cref="T:Abt.Controls.SciChart.Model.DataSeries.IPointSeries" />, the <see cref="T:Abt.Controls.SciChart.IndexRange" /> of points on the screen
/// and the current YAxis and XAxis <see cref="T:Abt.Controls.SciChart.Numerics.CoordinateCalculators.ICoordinateCalculator`1" /> to convert data-points to screen points</param>
protected override void InternalDraw(IRenderContext2D renderContext, IRenderPassData renderPassData)
{
Func<double, double, Color> func = null;
IPointSeries pointSeries = base.CurrentRenderPassData.PointSeries;
LinesEnumerable linesEnumerable = new LinesEnumerable(pointSeries, renderPassData.XCoordinateCalculator, renderPassData.YCoordinateCalculator, this.IsDigitalLine);
Func<Color, IPen2D> func2 = null;
FastLineRenderableSeries.ToolbarCollection toolbarCollection = new FastLineRenderableSeries.ToolbarCollection();
toolbarCollection.uriInstance = new DeploymentInvoker(renderContext, base.AntiAliasing, (float)base.StrokeThickness, base.Opacity);
try
{
if (base.SeriesColor.A != 0)
{
if (base.PaletteProvider != null)
{
IEnumerable<Point> arg_98_1 = linesEnumerable;
bool arg_98_2 = base.DrawNaNAs == LineDrawMode.ClosedLines;
if (func == null)
{
func = new Func<double, double, Color>(this.DisconnectSelection);
}
Func<double, double, Color> arg_98_3 = func;
if (func2 == null)
{
func2 = new Func<Color, IPen2D>(toolbarCollection.ViewReference);
}
renderContext.DrawLinesReusablePens(arg_98_1, arg_98_2, arg_98_3, func2);
}
else
{
this.DisconnectSelection(renderContext, base.SeriesColor, linesEnumerable, base.DrawNaNAs == LineDrawMode.ClosedLines, (this.StrokeDashArray != null) ? this.StrokeDashArray.ToArray<double>() : null);
}
}
}
finally
{
if (toolbarCollection.uriInstance != null)
{
((IDisposable)toolbarCollection.uriInstance).Dispose();
}
}
IPointMarker pointMarker = base.GetPointMarker();
if (pointMarker != null)
{
if (this.IsDigitalLine)
{
linesEnumerable = new LinesEnumerable(pointSeries, renderPassData.XCoordinateCalculator, renderPassData.YCoordinateCalculator, false);
}
using (IPen2D pen2D = renderContext.CreatePen(base.SeriesColor, base.AntiAliasing, (float)base.StrokeThickness, base.Opacity, null, PenLineCap.Round))
{
IPointMarker arg_156_0 = pointMarker;
IEnumerable<Point> arg_14E_0 = linesEnumerable;
if (FastLineRenderableSeries.CS$<>9__CachedAnonymousMethodDelegate4 == null)
{
FastLineRenderableSeries.CS$<>9__CachedAnonymousMethodDelegate4 = new Func<Point, bool>(FastLineRenderableSeries.DisconnectSelection);
}
arg_156_0.Draw(renderContext, arg_14E_0.Where(FastLineRenderableSeries.CS$<>9__CachedAnonymousMethodDelegate4), pen2D, null);
}
}
}

搜索DrawLinesReusablePens, 发现只有RenderContextBase实现IRenderContext2D接口, 然后DrawLinesReusablePens内部foreach遍历调用DrawLineSegment
后者调用DrawLine

那么谁调用了InternalDraw
protected override void InternalDraw 是虚方法,那么在父类BaseRenderableSeries中找, 发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void IDrawable.ProcessContext(IRenderContext2D uriInstance, IRenderPassData windowCache)
{
this.CurrentRenderPassData = windowCache;
if (this.IsValidForDrawing)
{
if (this.uriInstance != uriInstance.ViewportSize)
{
this.OnParentSurfaceViewportSizeChanged();
this.uriInstance = uriInstance.ViewportSize;
}
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
this.InternalDraw(uriInstance, windowCache);
stopwatch.Stop();
SciChartDebugLogger.Instance.WriteLine("{0} DrawTime: {1}ms", new object[]
{
base.GetType().Name,
stopwatch.ElapsedMilliseconds
});
}
}

而且没有找到OnDraw方法,初步推断IDrawable.ProcessContext就是OnDraw


结束了FastLineRenderableSeries和NumericAxis, 再来看
SciChartSurface
它是如何调用FastLineRenderableSeries和NumericAxis的OnDraw

SciChartSurface的父类是SciChartSurfaceBase (SciChartSurfaceBase 继承Control,base指Control),里面有一个虚方法

1
2
3
4
/// <summary>
/// The inner drawing loop. Called once per frame. Do your drawing here.
/// </summary>
protected abstract void DoDrawingLoop();

由于是protected,该方法在public virtual void InvalidateElement()中被用到,终于出现public了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// Asynchronously requests that the element redraws itself plus children.
/// Will be ignored if the element is ISuspendable and currently IsSuspended (within a SuspendUpdates/ResumeUpdates call)
/// </summary>
public virtual void InvalidateElement()
{
if (this.IsSuspended)
{
SciChartDebugLogger.Instance.WriteLine("SciChartSurface.IsSuspended=true. Ignoring InvalidateElement() call", new object[0]);
return;
}
if (!OptionsList.SearchOptions() && this.RenderPriority != RenderPriority.Immediate)
{
if (this.uriInstance != null)
{
this.uriInstance.InvalidateElement();
}
return;
}
this.Services.GetService<ProjectSettings>().SelectClient(new Action(this.DoDrawingLoop), DispatcherPriority.Normal);
}

InvalidateElement在SciChartSurfaceBase中的OnSciChartSurfaceLoaded中被调用, 后者在OnLoad中被调用

1
2
3
4
5
6
7
/// <summary>
/// Forces initialization of the SciChartSurface in the case it is being used to render off-screen (on server)
/// </summary>
public void OnLoad()
{
this.OnSciChartSurfaceLoaded();
}

回到SciChartSurface看DoDrawingLoop

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 DoDrawingLoop()
{
if (this.windowCache)
{
return;
}
try
{
using (IRenderContext2D renderContext = this.uriInstance.GetRenderContext()) // private IRenderSurface2D uriInstance;
{
try
{
RendererErrorCode rendererErrorCode = this.uriInstance.RenderLoop(renderContext); // private ISciChartRenderer uriInstance;
if (!GroupSettings.keywordsMap.Equals(rendererErrorCode) && base.DebugWhyDoesntSciChartRender)
{
Console.WriteLine(" SciChartSurface didn't render, " + rendererErrorCode);
}
}
catch (Exception caught)
{
base.OnRenderFault(caught);
}
}
}
catch (Exception caught2)
{
base.OnRenderFault(caught2);
}
}

搜索GetRenderContext和RenderLoop, 可知
private IRenderSurface2D uriInstance可以认为是HighSpeedRenderSurface
private ISciChartRenderer uriInstance;是ViewConverter


HighSpeedRenderSurface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace Abt.Controls.SciChart.Rendering.HighSpeedRasterizer
{
/// <summary>
/// Provides a <see cref="T:Abt.Controls.SciChart.Rendering.Common.RenderSurfaceBase" /> implementation that uses a High-Speed software rasterizer, capable of outputting many millions of points (line-series)
/// at interactive framerates. The downside is, the <see cref="T:Abt.Controls.SciChart.Rendering.HighSpeedRasterizer.HighSpeedRenderSurface" /> uses integer fixed-point math which results in jagged lines.
/// </summary>
/// <seealso cref="T:Abt.Controls.SciChart.Rendering.HighQualityRasterizer.HighQualityRenderSurface" />
/// <seealso cref="T:Abt.Controls.SciChart.Rendering.Common.RenderSurfaceBase" />
/// <seealso cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" />
[AbtLicenseProvider(typeof(XmlFileSite))]
public class HighSpeedRenderSurface : RenderSurfaceBase
{
/// <summary>
/// When overridden in a derived class, returns a RenderContext valid for the current render pass
/// </summary>
/// <returns></returns>
public override IRenderContext2D GetRenderContext()
{
return this.RenderWriteableBitmap.ViewReference(base.Image, this.uriInstance);
}
}
}

protected WriteableBitmap RenderWriteableBitmap;
WriteableBitmap
https://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap(v=vs.110).aspx
WriteableBitmap使用的双缓冲技术

1
2
3
4
Use the WriteableBitmap class to update and render a bitmap on a per-frame basis. This is useful for generating algorithmic content, such as a fractal image, and for data visualization, such as a music visualizer.
The WriteableBitmap class uses two buffers. The back buffer is allocated in system memory and accumulates content that is not currently displayed. The front buffer is allocated in system memory and contains the content that is currently displayed. The rendering system copies the front buffer to video memory for display.
Two threads use these buffers. The user interface (UI) thread generates the UI but does not present it to the screen. The UI thread responds to user input, timers, and other events. An application can have multiple UI threads. The render thread composes and renders changes from the UI thread. There is only one render thread per application.
The UI thread writes content to the back buffer. The render thread reads content from the front buffer and copies it to video memory. Changes to the back buffer are tracked with changed rectangular regions.

ViewReference是扩展方法, 经常出现这个ViewReference, 不是特别清楚意义


ViewConverter

internal class ViewConverter : ISciChartRenderer
看ViewConverter的RenderLoop方法

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
public RendererErrorCode RenderLoop(IRenderContext2D renderContext)
{
//...忽略
//
using (IUpdateSuspender updateSuspender = this.uriInstance.SuspendUpdates())
{
updateSuspender.ResumeTargetOnDispose = false;
SciChartDebugLogger.Instance.WriteLine("Beginning Render Loop ... ", new object[0]);
Size size = this.ViewReference(this.uriInstance);
if (this.ViewReference(this.uriInstance, size, out result))
{
RenderPassInfo renderPassInfo = ViewConverter.ViewReference(this.uriInstance, size);
renderContext.Clear();

//绘制坐标轴
ViewConverter.ViewReference(this.uriInstance, renderPassInfo, renderContext);

//绘制renderableSeries
ViewConverter.SelectClient(this.uriInstance, renderPassInfo, renderContext);

//更新Annotations
ViewConverter.ViewReference(this.uriInstance, renderPassInfo);
this.uriInstance.OnSciChartRendered();
}
}
return result;
}

绘制坐标轴, 出现OnDraw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal static void ViewReference(ISciChartSurface uriInstance, RenderPassInfo windowCache, IRenderContext2D lastKeywords)
{
foreach (IAxis current in uriInstance.XAxes)
{
current.ValidateAxis();
current.OnDraw(lastKeywords, null);
}
foreach (IAxis current2 in uriInstance.YAxes)
{
current2.ValidateAxis();
current2.OnDraw(lastKeywords, null);
}
lastKeywords.Layers.Flush();
}

遍历绘制renderableSeries

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
internal static void SelectClient(ISciChartSurface uriInstance, RenderPassInfo windowCache, IRenderContext2D lastKeywords)
{
if (windowCache.RenderableSeries == null)
{
return;
}
List<int> list = new List<int>();
for (int i = 0; i < windowCache.RenderableSeries.Length; i++)
{
IRenderableSeries renderableSeries = windowCache.RenderableSeries[i];
if (renderableSeries != null)
{
renderableSeries.XAxis = uriInstance.XAxes.GetAxisById(renderableSeries.XAxisId, true);
renderableSeries.YAxis = uriInstance.YAxes.GetAxisById(renderableSeries.YAxisId, true);
if (windowCache.RenderableSeries[i].IsSelected)
{
list.Add(i);
}
else
{
ViewConverter.ViewReference(uriInstance, windowCache, lastKeywords, i);
}
}
}
foreach (int current in list)
{
ViewConverter.ViewReference(uriInstance, windowCache, lastKeywords, current);
}
uriInstance.Services.GetService<IEventAggregator>().Publish<SciChartRenderedMessage>(new SciChartRenderedMessage(uriInstance, lastKeywords));
}

1
2
3
4
5
6
7
8
9
10
11
private static void ViewReference(ISciChartSurface uriInstance, RenderPassInfo windowCache, IRenderContext2D lastKeywords, int categoryDisposed)
{
IRenderableSeries renderableSeries = windowCache.RenderableSeries[categoryDisposed];
ICoordinateCalculator<double> yCoordinateCalculator;
ICoordinateCalculator<double> xCoordinateCalculator;
if (windowCache.YCoordinateCalculators.TryGetValue(renderableSeries.YAxisId, out yCoordinateCalculator) && windowCache.XCoordinateCalculators.TryGetValue(renderableSeries.XAxisId, out xCoordinateCalculator))
{
RenderPassData renderPassData = new RenderPassData(windowCache.IndicesRanges[categoryDisposed], xCoordinateCalculator, yCoordinateCalculator, windowCache.PointSeries[categoryDisposed]);
renderableSeries.OnDraw(lastKeywords, renderPassData);
}
}

最后看一下IRenderContext2D和IRenderPassData

IRenderContext2D

相当于Winform中的Graphics

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
namespace Abt.Controls.SciChart.Rendering.Common
{
//删掉了一些
/// <summary>
/// Defines the interface to a 2D RenderContext, allowing drawing, blitting and creation of pens and brushes on the <see cref="T:Abt.Controls.SciChart.Rendering.Common.RenderSurfaceBase" />
/// </summary>
/// <remarks>The <see cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" /> is a graphics context valid for the current render pass. Any class which implements <see cref="T:Abt.Controls.SciChart.Visuals.IDrawable" /> has an OnDraw method
/// in which an <see cref="T:Abt.Controls.SciChart.Rendering.Common.IRenderContext2D" /> is passed in. Use this to draw penned lines, fills, rectangles, ellipses and blit graphics to the screen.</remarks>
public interface IRenderContext2D : IDisposable
{
/// <summary>
/// Gets a collection of <see cref="T:Abt.Controls.SciChart.Rendering.Common.RenderOperationLayers" />, which allow rendering operations to be posted to a layered queue for later
/// execution in order (and correct Z-ordering).
/// </summary>
/// <example>
/// <code title="RenderOperationLayers Example" description="Demonstrates how to enqueue operations to the RenderOperationLayers collection and later flush to ensure rendering operations get processed in the correct Z-order" lang="C#">
/// RenderOperationLayers layers = renderContext.Layers;
///
/// // Enqueue some operations in the layers in any order
/// layers[RenderLayer.AxisMajorGridlines].Enqueue(() =&gt; renderContext.DrawLine(/* .. */));
/// layers[RenderLayer.AxisBands].Enqueue(() =&gt; renderContext.DrawRectangle(/* .. */));
/// layers[RenderLayer.AxisMinorGridlines].Enqueue(() =&gt; renderContext.DrawLine(/* .. */));
///
/// // Processes all layers by executing enqueued operations in order of adding,
/// // and in Z-order of layers
/// layers.Flush();</code>
/// </example>
RenderOperationLayers Layers
{
get;
}

/// <summary>
/// Gets the current size of the viewport.
/// </summary>
Size ViewportSize
{
get;
}

IBrush2D CreateBrush(Color color, double opacity = 1.0, bool? alphaBlend = null);

IPen2D CreatePen(Color color, bool antiAliasing, float strokeThickness, double opacity = 1.0, double[] strokeDashArray = null, PenLineCap strokeEndLineCap = PenLineCap.Round);

ISprite2D CreateSprite(FrameworkElement fe);

void Clear();

void DrawSprite(Point destPoint, ISprite2D srcSprite, Rect srcRect);

void FillRectangle(IBrush2D brush, Point pt1, Point pt2, double gradientRotationAngle);

void FillPolygon(IBrush2D brush, IEnumerable<Point> points);

void FillArea(IBrush2D brush, IEnumerable<Tuple<Point, Point>> lines, double gradientRotationAngle = 0.0, bool isVerticalChart = false);

void DrawQuad(IPen2D pen, Point pt1, Point pt2);

void DrawEllipse(double width, double height, Point center, IBrush2D brush, IPen2D pen);

void DrawLine(IPen2D pen, Point pt1, Point pt2);

}


IRenderPassData

这个接口将原始数据变换到Chart所在的视窗上

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
using Abt.Controls.SciChart.Model.DataSeries;
using Abt.Controls.SciChart.Numerics.CoordinateCalculators;
using System;

namespace Abt.Controls.SciChart.Visuals.RenderableSeries
{
/// <summary>
/// Defines the interface to <see cref="T:Abt.Controls.SciChart.Visuals.RenderableSeries.RenderPassData" />, the data used in a single render pass by <see cref="T:Abt.Controls.SciChart.Visuals.RenderableSeries.BaseRenderableSeries" /> derived types
/// </summary>
public interface IRenderPassData
{
/// <summary>
/// Gets the integer indices of the X-Data array that are currently in range.
/// </summary>
/// <returns>The indices to the X-Data that are currently in range</returns>
/// <example>If the input X-data is 0...100 in steps of 1, the VisibleRange is 10, 30 then the PointRange will be 10, 30</example>
/// <remarks></remarks>
IndexRange PointRange
{
get;
}

/// <summary>
/// Gets the current point series.
/// </summary>
IPointSeries PointSeries
{
get;
}

/// <summary>
/// Gets a value, indicating whether current chart is vertical
/// </summary>
bool IsVerticalChart
{
get;
}

/// <summary>
/// Gets the current Y coordinate calculator.
/// </summary>
ICoordinateCalculator<double> YCoordinateCalculator
{
get;
}

/// <summary>
/// Gets the current X coordinate calculator.
/// </summary>
ICoordinateCalculator<double> XCoordinateCalculator
{
get;
}
}
}

P&P笔记(Z3)工具使用(三)(totalcommander)

Posted on 2017-01-22 | In 编程

文件(夹)操作

  • 文件数字名称排序(显示成1、10、2、20、3、30、4、40, 按理显示成1、2、3、4、10、20、30、40)
    • wincmd.ini中修改为SortUpper=2
  • 显示SVN图标
    1. SVN Settings->Icon Overlays中Show overlays and context menu only in explorer没有选中
    2. TC中在设置->选项->显示->图标中把显示覆盖图标选上
  • 压缩
    • alt+F5
  • 解压
    • alt+F9
    • backspace 解压到当前目录
  • 新建文件
    • shift+F4
    • F4 (默认用notepad.exe打开)
  • 右键菜单: shift+F9

  • 文件属性窗口: alt+enter

  • 文件大小: ctrl+L

  • 搜索插件: QuickSearch eXtended

页签操作

  • 新建页签: ctrl+T
  • 左右窗口焦点切换: tab
  • 后退和前进: alt+方向键
  • 常用目录: ctrl+D

其他

  • 命令行
    • 按下→方向键,将光标定位到命令行
    • chrome下载完文件后打开文件所在目录,复制该目录的路径,在TC的命令行中先输入命令cd,然后粘贴路径,这样就可以在TC中快速到达该目录。
1…567

zmapleaf

67 posts
2 categories
11 tags
© 2020 zmapleaf
Powered by Hexo
|
Theme — NexT.Mist v5.1.4