1. 程式人生 > 其它 >WPF開發隨筆收錄-DrawingVisual繪製高效能曲線圖

WPF開發隨筆收錄-DrawingVisual繪製高效能曲線圖

一、前言

專案中涉及到了心率檢測,而且資料量達到了百萬級別,通過WPF實現大資料曲線圖時,嘗試過最基礎的Canvas來實現,但是效能堪憂,而且全部畫出來也不實際。同時也嘗試過找第三方的開源庫,但是因為曲線圖涉及到很多細節功能,第三方的肯定也沒法滿足。沒辦法,只能自己實現,上網查詢後發現DrawingVisual這個玩意可以實現高效能畫圖,同時再搭配區域性顯示,這樣就能實現自己想要的效果。話不多說,今天把大致的實現思路寫一下,就不直接把專案的原始碼貼出來,寫個簡單的Demo就好了。

二、正文

1、首先新建個專案,然後建立個自定義控制元件,命名為CurveChartDrawingVisual,然後讓它繼承FrameworkElement。因為要使用DrawingVisual物件的話,需要為它建立一個主機容器。關於其他相關DrawingVisual的細節這裡不做過多闡述,不明白的可以去微軟官網看。

2、實現的具體程式碼如下,相關細節有備註標註了。這裡記得要重寫一下VisualChildrenCount屬性和重寫GetVisualChild()方法,不然圖畫不出來

public class CruveChartDrawingVisual : FrameworkElement
{
    private List<Visual> visuals = new List<Visual>();
    private DrawingVisual Layer;

    private double offset_x = 0;//滑動條偏移值
    private double
y_scale;//y軸放心縮放比例 private List<int> list_points;//曲線資料 private static int Top_Val_Max = 100;//y軸最大值 private static int Top_Val_Min = 0;//y軸最小值 private static int X_Sex = 20;//x軸分度值 private static int Y_Sex = 20;//x軸分度值 private static int Bottom = 30;//底部x軸座標顯示高度 Pen pen = new Pen(Brushes.Green, 2
); Pen primarygrid_pen = new Pen(Brushes.Black, 1); Pen secondgrid_pen = new Pen(Brushes.Gray, 1); public CruveChartDrawingVisual() { pen.Freeze();//凍結筆,提高效能關鍵所在 primarygrid_pen.Freeze(); secondgrid_pen.Freeze(); Layer = new DrawingVisual(); visuals.Add(Layer); } public void SetupData(List<int> points) { list_points = points; offset_x = 0; DrawContent(); } public void OffsetX(double offset) { offset_x = offset; DrawContent(); InvalidateVisual(); } private void DrawContent() { var dc = Layer.RenderOpen(); y_scale = (RenderSize.Height - Bottom) / (Top_Val_Max - Top_Val_Min); var mat = new Matrix(); mat.ScaleAt(1, -1, 0, RenderSize.Height / 2); mat.OffsetX = -offset_x; dc.PushTransform(new MatrixTransform(mat)); //橫線 for (int y = 0; y <= Top_Val_Max - Top_Val_Min; y += 10) { Point point1 = new Point(offset_x, y * y_scale + Bottom); Point point2 = new Point(offset_x + RenderSize.Width, y * y_scale + Bottom); if (y % Y_Sex == 0) { dc.DrawLine(primarygrid_pen, point1, point2); continue; } dc.DrawLine(secondgrid_pen, point1, point2); } //豎線與文字 for (int i = 0; i <= (offset_x + RenderSize.Width); i += X_Sex * 2) { if (i < offset_x) { continue; } var point1 = new Point(i, Bottom); var point2 = new Point(i, (Top_Val_Max - Top_Val_Min) * y_scale + Bottom); //y軸文字 if (i % 100 == 0) { var text1 = new FormattedText(i + "", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Verdana"), 16, Brushes.Black); var mat3 = new Matrix(); mat3.ScaleAt(1, -1, i - text1.Width / 2, 8 + text1.Height / 2); dc.PushTransform(new MatrixTransform(mat3)); dc.DrawText(text1, new Point(i - text1.Width / 2, 8)); dc.Pop(); } //表格刻度文字 if (i % 100 == 0) { for (int y = Top_Val_Min; y <= Top_Val_Max; y += 10) { if (y % Y_Sex == 0) { var text1 = new FormattedText(y + "", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Verdana"), 12, Brushes.Black); var mat3 = new Matrix(); mat3.ScaleAt(1, -1, i + 1, (y - Top_Val_Min) * y_scale + Bottom + text1.Height / 2); dc.PushTransform(new MatrixTransform(mat3)); dc.DrawText(text1, new Point(i + 1, (y - Top_Val_Min) * y_scale + Bottom)); dc.Pop(); } } //深色豎線 dc.DrawLine(primarygrid_pen, point1, point2); continue; } //淺色豎線 dc.DrawLine(secondgrid_pen, point1, point2); } if (list_points != null) { for (int i = (int)offset_x; i < list_points.Count - 1; i++) { if (i > offset_x + RenderSize.Width) { break; } dc.DrawLine(pen, new Point(i, list_points[i] * y_scale + Bottom), new Point(i + 1, list_points[i + 1] * y_scale + Bottom)); } } dc.Pop(); dc.Close(); } protected override int VisualChildrenCount => visuals.Count; protected override Visual GetVisualChild(int index) { return visuals[index]; } protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { DrawContent(); base.OnRenderSizeChanged(sizeInfo); } protected override void OnRender(DrawingContext drawingContext) { drawingContext.DrawRectangle(Brushes.White, null, new Rect(0, 0, RenderSize.Width, RenderSize.Height)); base.OnRender(drawingContext); } }

3、接著測試一下,開啟MainWindow,新增我們的自定義控制元件,這裡區域性顯示需要搭配一個ScrollViewer來實現,程式碼如下

<Grid>
    <local:CruveChartDrawingVisual x:Name="curve" Margin="0,15,0,20" />
    <ScrollViewer
        Name="scroll"
        HorizontalScrollBarVisibility="Auto"
        ScrollChanged="ScrollViewer_ScrollChanged"
        VerticalScrollBarVisibility="Disabled">
        <Canvas x:Name="canvas" Height="1" />
    </ScrollViewer>
</Grid>

4、接著就是後臺程式碼,比較簡單,就是自動生成一個List,然後傳給自定義控制元件,Canvas的寬度直接設定為List的長度,因為這裡是水平方向一個畫素點畫一個點,然後滑動條滑動時再將對應的偏移值傳遞到控制元件,再通過偏移值更新檢視

public partial class MainWindow : Window
{
    private bool isAdd = true;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        List<int> lists = new List<int>();
        int temp = 20;
        for (int i = 0; i < 60 * 60; i++)
        {
            if (isAdd)
            {
                lists.Add(temp);
                temp ++;
            }
            else
            {
                lists.Add(temp);
                temp --;
            }

            if (temp == 90) isAdd = false;
            if (temp == 10) isAdd = true;
        }

        canvas.Width = lists.Count;

        curve.SetupData(lists);
    }

    private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        curve.OffsetX(scroll.HorizontalOffset);
    }
}

5、執行效果如下,滑動條拖到哪裡就顯示哪裡,這樣就算資料量再大也沒問題