WPF动态数据显示 - 使用标记绘图速度缓慢

3

我在使用标记时,等待D3中的ChartPlotter显示出来非常困难。当然,我正在尝试绘制无数条记录(嗯,70万条记录)。当仅使用线条时,一切正常(大约20秒)。但是当使用标记时,需要等待5分钟左右。这是不能接受的。

有什么想法吗?

下面是我的操作步骤,其中包含解释:

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;
    }

说明:

  1. 我隐藏了绘图机图例并使用ShowLegend制作自己的图例,因为如果只有标记,图例不会显示(我错了吗?)
  2. 我最小化和最大化绘图窗口,否则绘图不会更新,或者更新但不会保存到文件。如果我移动窗口(我猜是某种重画事件),这也有效,但由于过程是自动的,用户没有任何交互。我尝试了Invalidate,但无效。有什么想法吗?

谢谢!


另外,我刚刚注意到...你的颜色列表中有两个图丽色! - Jason Higgins
与您的问题无关,我的D3库版本没有ChartPlotter.MainHorizontalAxis属性(也没有AddChild)。我从codeplex下载了该库,我的版本是0.3。是否有更新的版本?谢谢。 - Maurizio Macagno
2个回答

3

我写了一个类来隐藏屏幕外的标记。这是一种虚拟化技术,当屏幕上没有大量标记时,可以将性能提高十倍。它看起来像这样:

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);
    }
}

请确保在XAML中调用FilteredMarkerPointsGraph而不是MarkerPointsGraph!
编辑:
1. 对于带有标记的图例,我不确定您需要什么,实际上我没有在任何图形中使用图例,但是您的解决方案似乎是可以的。
2. 重新绘制绘图实际上很容易。我发现最好的方法是在代码后台中拥有一个表示数据源的属性,并将图表的数据源绑定到该属性。让你的代码后台实现INotifyPropertyChanged,并在每次更新或重新分配数据源时调用OnPropertyChanged。这将强制绘图程序观察绑定并重新绘制您的图形。
示例:
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));
}  

关于您使用标记的性能问题,很难确定具体是什么原因造成了性能问题。但是我建议尝试使用不同的数据源。我一直在使用EnumerableDataSource,它总是非常可靠。尝试将您的数据引入一个单一对象,并在set块中设置映射,如上所示:

value.SetYMapping(k => Convert.ToDouble(k.yData));
value.SetXMapping(k => Convert.ToDouble(k.xData));

您唯一需要担心的是Enumerable数据源中的映射,而D3应该帮您处理其余部分。


我刚看到你在Felice的帖子上的评论...如果你无法操作图形,我不确定这是否是你要寻找的答案。这只是将屏幕外的点虚拟化。也许你可以更详细地解释一下你的项目,可能会有更好的解决方案? - Jason Higgins
Jason:再次感谢。我正在为一种电子流程编写自动报告生成器。我想提供用户一个表格,其中包含所有图形,用户只需添加解释即可,而不是打开新的Word文档,从头开始编写,并在Matlab中制作自己的图形,然后再分析结果。大多数数据都是电子状态文件,有成千上万条记录。希望这能解释清楚。再次感谢。 - Gabe
好的,如果你坚持的话:http://stackoverflow.com/questions/13437250/manipulating-simple-bookmarks-in-word - Gabe
实际上,Jason,我采用了你的想法并切换到EnumerableDataSource。绘图速度更快了,但是当我像上面的代码一样使用标记时,仍然需要几分钟...我想我要放弃标记了。 - Gabe
嗯,我慢慢地开始没有更多的想法了。哈哈。你确定是绘制标记使你的速度变慢,而不是处理标记所代表的数据吗?如果是这样,那么你的选择似乎只能缩小图表的起始缩放级别,并将标记绘制虚拟化为仅绘制屏幕上的内容,就像我上面的回答一样。 - Jason Higgins
显示剩余8条评论

1

当您显示“Gazillion”个点时,用户可能无法看到标记,您可以在缩放级别更合理时将模式从线路切换到标记吗?


1
嗨,费利斯。用户不操作UI界面。我只是制作图表并将其粘贴到Word中。我可以很清楚地看到标记,并且它对我很有用。 - Gabe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接