官方
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的基类BackgroundBitmapRenderer1
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
218using 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
9while (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后,
- 将tasks中的其他taskId去掉, 只剩下1个;//while循环
- 将剩下的1个taskId作为参数, 调用EnqueueTask;//if语句
剩1个, 双核的话, maxTasks为11
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 | public long Plot(double[,] data, double[] x, double[] y, double missingValue) |
作者这里用reactive extension的Subject(Subject既是IObservable也是IObserver)对序列进行过滤
当完成的RenderCompletion中的taskId 等于 Plot返回的id, 表示当前Plot绘制完成, 转而通知work thread继续生成新的数据
然后进行下一次Plot; 这样tasks中不会累积1,2,3,4
1 | private void PlotData() |
前一种方式和后一种方式都值得学习