WPF DynamicDataDisplay - 使用标记缓慢绘图
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13378180/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WPF DynamicDataDisplay - Slow plotting with Markers
提问by Gabe
I am having a really hard time waiting for the ChartPlotter in D3 to show itself, when using markers. Of course I am trying to plot a Gazillion records (well, 700,000 records). When using just a line, all is well (20 seconds or so). When using markers, we're talking 5 minutes. That's not acceptable.
在使用标记时,我真的很难等待 D3 中的 ChartPlotter 显示出来。当然,我正在尝试绘制 Gazillion 记录(好吧,700,000 条记录)。当只使用一条线时,一切都很好(20 秒左右)。使用标记时,我们说的是 5 分钟。那是不能接受的。
Any ideas?
有任何想法吗?
Here's what I have done, with explanations under it.
这是我所做的,下面有解释。
public static string MakeSimplePlot(double[][] xData, double[][] yData, string[] legend, string xAxisTitle, string yAxisTitle, bool[] showLines, bool[] showMarkers)
{
ChartPlotter plotter = new ChartPlotter();
plotter.MainHorizontalAxis = new HorizontalAxis();
plotter.MainVerticalAxis = new VerticalAxis();
HorizontalAxisTitle horizontalAxisTitle = new HorizontalAxisTitle();
horizontalAxisTitle.Content = xAxisTitle;
plotter.AddChild(horizontalAxisTitle);
VerticalAxisTitle verticalAxisTitle = new VerticalAxisTitle();
verticalAxisTitle.Content = yAxisTitle;
plotter.AddChild(verticalAxisTitle);
Color[] plotColors = new Color[13] { Colors.Blue, Colors.Red, Colors.Green, Colors.Chartreuse, Colors.Yellow, Colors.Violet, Colors.Tan, Colors.Silver, Colors.Salmon, Colors.Lime, Colors.Brown, Colors.Chartreuse, Colors.DarkGray };
for (int seriesCounter = 0; seriesCounter < legend.Count(); seriesCounter++)
{
DataFile clearedInputs = ClearExcess(new DataFile(xData[seriesCounter], yData[seriesCounter]));
xData[seriesCounter] = clearedInputs.time;
yData[seriesCounter] = clearedInputs.data;
var xDataSource = new EnumerableDataSource<double>(xData[seriesCounter]);
xDataSource.SetXMapping(x => x);
var yDataSource = new EnumerableDataSource<double>(yData[seriesCounter]);
yDataSource.SetYMapping(x => x);
CompositeDataSource plotSeries = new CompositeDataSource(xDataSource, yDataSource);
CirclePointMarker circlePointMarker = new CirclePointMarker();
circlePointMarker.Fill = new SolidColorBrush(plotColors[seriesCounter]);
circlePointMarker.Pen = new Pen(circlePointMarker.Fill, 0);
circlePointMarker.Size = (showMarkers[seriesCounter] == false) ? 0 : 8;
int lineWidth = (showLines[seriesCounter] == false) ? 0 : 2;
if (showMarkers[seriesCounter] == false)
{
plotter.AddLineGraph(plotSeries, new Pen(circlePointMarker.Fill, lineWidth), new PenDescription("Dummy"));
}
else
{
plotter.AddLineGraph(plotSeries, new Pen(circlePointMarker.Fill, lineWidth), circlePointMarker, new PenDescription("Dummy"));
}
}
UIParameters.plotWindow.mainGrid.Children.Clear();
UIParameters.plotWindow.mainGrid.RowDefinitions.Clear();
UIParameters.plotWindow.mainGrid.Children.Add(plotter);
plotter.Viewport.FitToView();
plotter.LegendVisible = false;
plotter.NewLegendVisible = false;
if (legend.Count() > 1)
{
ShowLegend(legend, plotColors);
}
UIParameters.plotWindow.WindowState = WindowState.Minimized;
UIParameters.plotWindow.WindowState = WindowState.Normal;
string filename = Path.ChangeExtension(Path.GetTempFileName(), "png");
RenderTargetBitmap targetBitmap = new RenderTargetBitmap((int)UIParameters.plotWindow.mainGrid.ActualWidth, (int)UIParameters.plotWindow.mainGrid.ActualHeight, 96d, 96d, PixelFormats.Default);
targetBitmap.Render(UIParameters.plotWindow.mainGrid);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(targetBitmap));
using (var fileStream = File.Open(filename, FileMode.OpenOrCreate))
{
encoder.Save(fileStream);
UIParameters.plotWindow.mainGrid.Clip = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
targetBitmap.Freeze();
if (targetBitmap != null) targetBitmap.Clear();
targetBitmap = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
return filename;
}
Explanations:
说明:
- I hide the plotter legend and make my own using ShowLegend, since the legend does not show if it has only markers (am I wrong?)
- I minimize and maximize the plot window, since otherwise the plot does not update, or it updates but does not get saved to a file. This also works if I move the window (I guess some kind of redraw event), but since the process is autoamatic, the user does not have any interaction. I tries Invalidate, to no avail. Ideas?
- 我隐藏绘图仪图例并使用 ShowLegend 制作我自己的图例,因为图例不显示它是否只有标记(我错了吗?)
- 我最小化和最大化绘图窗口,否则绘图不会更新,或者它会更新但不会保存到文件中。如果我移动窗口(我猜是某种重绘事件),这也有效,但由于该过程是自动的,因此用户没有任何交互。我尝试无效,但无济于事。想法?
Thanks!
谢谢!
采纳答案by Jason Higgins
I wrote my own class to hide markers when they are off screen. It's a virtualization technique that speeds up performance tenfold when you don't have tons of markers on screen. It looks like this :
我写了我自己的类来隐藏屏幕外的标记。当屏幕上没有大量标记时,这是一种虚拟化技术,可将性能提高十倍。它看起来像这样:
using System;
using System.Windows;
using System.Windows.Media;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using Microsoft.Research.DynamicDataDisplay.PointMarkers;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay.Charts {
public class FilteredMarkerPointsGraph : MarkerPointsGraph {
public FilteredMarkerPointsGraph()
: base() {
;
}
public FilteredMarkerPointsGraph(IPointDataSource dataSource)
: base(dataSource) {
;
}
protected override void OnRenderCore(DrawingContext dc, RenderState state) {
// base.OnRenderCore
if (DataSource == null) return;
if (Marker == null) return;
var left = Viewport.Visible.Location.X;
var right = Viewport.Visible.Location.X + Viewport.Visible.Size.Width;
var top = Viewport.Visible.Location.Y;
var bottom = Viewport.Visible.Location.Y + Viewport.Visible.Size.Height;
var transform = Plotter2D.Viewport.Transform;
DataRect bounds = DataRect.Empty;
using (IPointEnumerator enumerator = DataSource.GetEnumerator(GetContext())) {
Point point = new Point();
while (enumerator.MoveNext()) {
enumerator.GetCurrent(ref point);
if (point.X >= left && point.X <= right && point.Y >= top && point.Y <= bottom)
{
enumerator.ApplyMappings(Marker);
Point screenPoint = point.DataToScreen(transform);
bounds = DataRect.Union(bounds, point);
Marker.Render(dc, screenPoint);
}
}
}
Viewport2D.SetContentBounds(this, bounds);
}
}
Make sure you call FilteredMarkerPointsGraph in the XAML instead of MarkerPointsGraph!
确保在 XAML 中调用 FilteredMarkerPointsGraph 而不是 MarkerPointsGraph!
EDIT
编辑
I'm not sure what you need with the legend with markers, I've not actually used a legend in any of my graphs, but your solution seems to be fine.
Redrawing the plot is quite easy actually.
我不确定带标记的图例需要什么,我实际上并没有在我的任何图表中使用图例,但您的解决方案似乎很好。
重绘情节其实很容易。
The best way that I have found to do this, is to have a Property in your code behind that represents the DataSource and bind the chart's DataSource to that property. Have your code behind implement INotifyPropertyChanged and call OnPropertyChanged every time you update or re-assign your data source. This will force the plotter to observe the binding and redraw your graph.
我发现这样做的最佳方法是在您的代码背后有一个属性来表示数据源并将图表的数据源绑定到该属性。让您的代码背后实现 INotifyPropertyChanged 并在每次更新或重新分配数据源时调用 OnPropertyChanged。这将强制绘图仪观察绑定并重新绘制图形。
Example:
例子:
EnumerableDataSource<Point> m_d3DataSource;
public EnumerableDataSource<Point> D3DataSource {
get {
return m_d3DataSource;
}
set {
//you can set your mapping inside the set block as well
m_d3DataSource = value;
OnPropertyChanged("D3DataSource");
}
}
protected void OnPropertyChanged(PropertyChangedEventArgs e) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, e);
}
}
protected void OnPropertyChanged(string propertyName) {
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
And about your performance with your markers.. It's hard to pinpoint exactly what would be causing your performance issue, but my recommendation is to try using a different data source. I've been using EnumerableDataSource and it's always worked like a charm. Try bringing in your data in a singular object and setting the mapping in your set block like as shown above using:
以及关于您使用标记的表现.. 很难准确指出是什么导致了您的表现问题,但我的建议是尝试使用不同的数据源。我一直在使用 EnumerableDataSource 并且它总是很有魅力。尝试在单个对象中引入您的数据并在您的 set 块中设置映射,如上所示使用:
value.SetYMapping(k => Convert.ToDouble(k.yData));
value.SetXMapping(k => Convert.ToDouble(k.xData));
The only thing you have to worry about is the mapping in Enumerable data source and D3 should handle the rest for you.
您唯一需要担心的是 Enumerable 数据源中的映射,D3 应该为您处理其余的事情。
回答by Felice Pollano
Well user can't probably see markers anyway when you are displaying the "Gazillion" of points: can't you switch the mode from line to markers when the zoom level is more reasonable ?
好吧,当您显示“无数”点时,用户可能无论如何都看不到标记:当缩放级别更合理时,您不能将模式从线切换到标记吗?

