InteractiveDataDisplay.WPF 制作动态图表作为值记录器外观

Posted

技术标签:

【中文标题】InteractiveDataDisplay.WPF 制作动态图表作为值记录器外观【英文标题】:InteractiveDataDisplay.WPF making dynamically chart as values recorder appearance 【发布时间】:2018-05-13 05:03:39 【问题描述】:

我使用 Microsoft InteractiveDataDisplay.WPF(以前的 DynamicDataDisplay)来可视化实时数据(大约 2-3 秒)。 此代码 xaml.cs:

public partial class MainWindow : Window
    
        public MainWindow()
        
            InitializeComponent();
            double[] y = new double[200];
            double[] x = new double[200];
            for (int i = 0; i < 200; i++)
            
                y[i] = 3.1415 * i / (y.Length - 1);
                x[i] = DateTime.Now.AddMinutes(-i).ToOADate();
            
            linegraph.Plot(x, y);
        
    

使用此 xaml:

<d3:Chart Name="plotter">
            <d3:Chart.Title>
                <TextBlock HorizontalAlignment="Center" FontSize="18" Margin="0,5,0,5">chart sample</TextBlock>                
            </d3:Chart.Title>
            <d3:LineGraph x:Name="linegraph" Description="Simple linegraph" Stroke="Blue" StrokeThickness="3">
            </d3:LineGraph>
        </d3:Chart>

给出这个观点: 但我想要以下自定义图表:

任何想法如何做到这一点?谢谢!

更新 1(使用 Kevin Ross 解决方案):

更新 2(使用 Dmitry Voytsekhovskiy 解决方案):

但时间轴 (Y) 不同步且不随数据移动。如何解决这个问题?

【问题讨论】:

【参考方案1】:

如何将轴移到顶部?

图表布局模板在Themes/Generic.xaml 中定义为d3:Chart 的样式。

您可以创建自定义样式,其中水平轴位于顶部 (d3:Figure.Placement="Top") 并具有正确的方向 (AxisOrientation="Top")。例如,

<d3:PlotAxis x:Name="PART_horizontalAxis"
         d3:Figure.Placement="Top" 
         AxisOrientation="Top"
         Foreground="TemplateBinding Foreground">
   <d3:MouseNavigation IsVerticalNavigationEnabled="False"/>
</d3:PlotAxis>

如何为轴标签使用自定义格式?

例如,如果沿 y 的值实际上是自某个时刻以来的小时数,并且您希望将轴刻度显示为 HH:mm,则需要将自定义 label provider 注入到 axis control 中。

为此,您可以创建从Axis 派生的新轴类并将自定义标签提供程序传递给基本构造函数:

public class CustomLabelProvider : ILabelProvider

    public static DateTime Origin = new DateTime(2000, 1, 1);

    public FrameworkElement[] GetLabels(double[] ticks)
    
        if (ticks == null)
            throw new ArgumentNullException("ticks");


        List<TextBlock> Labels = new List<TextBlock>();
        foreach (double tick in ticks)
        
            TextBlock text = new TextBlock();
            var time = Origin + TimeSpan.FromHours(tick);
            text.Text = time.ToShortTimeString();
            Labels.Add(text);
        
        return Labels.ToArray();
    


public class CustomAxis : Axis

    public CustomAxis() : base(new CustomLabelProvider(), new TicksProvider())
    
    

现在返回自定义图表模板并将垂直轴的类型从PlotAxis 更改为CustomAxis(请注意,您可能需要更改类型前缀):

<d3:CustomAxis x:Name="PART_verticalAxis"
             d3:Figure.Placement="Left" 
             AxisOrientation="Left"
             Foreground="TemplateBinding Foreground">
    <d3:MouseNavigation IsHorizontalNavigationEnabled="False"/>
</d3:CustomAxis>

如果我们对LineGraphSample 执行上述步骤并运行它,我们会得到以下结果:

最后是自定义图表样式:

<Style TargetType="d3:Chart">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="d3:Chart">
                <Grid>
                    <d3:Figure x:Name="PART_figure" Margin="1"
                               PlotHeight="Binding PlotHeight, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               PlotWidth="Binding PlotWidth, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               PlotOriginX="Binding PlotOriginX, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               PlotOriginY="Binding PlotOriginY, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               IsAutoFitEnabled="Binding IsAutoFitEnabled, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               AspectRatio="Binding AspectRatio, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               ExtraPadding="TemplateBinding BorderThickness"
                               Background="TemplateBinding Background">
                        <d3:MouseNavigation IsVerticalNavigationEnabled="TemplateBinding IsVerticalNavigationEnabled"
                                            IsHorizontalNavigationEnabled="TemplateBinding IsHorizontalNavigationEnabled"
                                            x:Name="PART_mouseNavigation"/>
                        <d3:KeyboardNavigation IsVerticalNavigationEnabled="TemplateBinding IsVerticalNavigationEnabled"
                                               IsHorizontalNavigationEnabled="TemplateBinding IsHorizontalNavigationEnabled"
                                               x:Name="PART_keyboardNavigation"/>
                        <d3:VerticalContentControl d3:Figure.Placement="Left"
                                                   Content="TemplateBinding LeftTitle"
                                                   VerticalAlignment="Center"
                                                   IsTabStop="False"/>
                            <d3:CustomAxis x:Name="PART_verticalAxis"
                                     d3:Figure.Placement="Left" 
                                     AxisOrientation="Left"
                                     Foreground="TemplateBinding Foreground">
                            <d3:MouseNavigation IsHorizontalNavigationEnabled="False"/>
                        </d3:CustomAxis>
                        <d3:AxisGrid x:Name="PART_axisGrid"
                                     VerticalTicks="Binding Ticks,ElementName=PART_verticalAxis, Mode=OneWay"
                                     HorizontalTicks="Binding Ticks,ElementName=PART_horizontalAxis, Mode=OneWay"
                                     Stroke="TemplateBinding Foreground" Opacity="0.25"/>
                        <ContentControl d3:Figure.Placement="Top" 
                                        HorizontalAlignment="Center"
                                        FontSize="16"
                                        Content="TemplateBinding Title"
                                        Foreground="TemplateBinding Foreground"
                                        IsTabStop="False"/>
                        <ContentControl d3:Figure.Placement="Bottom" 
                                        HorizontalAlignment="Center"
                                        Content="TemplateBinding BottomTitle"
                                        Foreground="TemplateBinding Foreground"
                                        IsTabStop="False"/>
                        <d3:VerticalContentControl d3:Figure.Placement="Right"
                                                   Content="TemplateBinding RightTitle"
                                                   VerticalAlignment="Center"
                                                   IsTabStop="False"/>
                        <d3:PlotAxis x:Name="PART_horizontalAxis"
                                     d3:Figure.Placement="Top" 
                                     AxisOrientation="Top"
                                     Foreground="TemplateBinding Foreground">
                            <d3:MouseNavigation IsVerticalNavigationEnabled="False"/>
                        </d3:PlotAxis>
                        <ContentPresenter/>
                        <Border BorderThickness="TemplateBinding BorderThickness"
                                BorderBrush="TemplateBinding Foreground" d3:Figure.Placement="Center"/>
                        <d3:Legend x:Name="PART_legend" 
                                   Foreground="Black" Content="TemplateBinding LegendContent"
                                   Visibility="TemplateBinding LegendVisibility"/>
                    </d3:Figure>
                    <Rectangle x:Name="FocusVisualElement" RadiusX="2" RadiusY="2" Stroke="#FF6DBDD1" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="IsTabStop" Value="False"/>
</Style>

【讨论】:

谢谢德米特里!但在您的示例中,时间轴 (Y) 不会随 X 轴上的数据值移动。如何解决这个问题?见更新 2 Dmitry,如何用数据移动时间轴(Y)? 如何用数据移动时间轴(Y)?【参考方案2】:

这是我想出的,它的边缘很粗糙,但应该可以帮助你。您对 XAML 的看法基本保持不变 我刚刚添加了一个按钮来启动和停止操作

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Button Click="Button_Click" Content="GO"/>

    <d3:Chart Name="plotter" Grid.Row="1">
        <d3:Chart.Title>
            <TextBlock HorizontalAlignment="Center" FontSize="18" Margin="0,5,0,5">chart sample</TextBlock>
        </d3:Chart.Title>
        <d3:LineGraph x:Name="linegraph" Description="Simple linegraph" Stroke="Blue" StrokeThickness="3">

        </d3:LineGraph>
    </d3:Chart>
</Grid>

你后面的代码就变成了

public partial class LiveView : Window

    private const int DataPointsToShow = 100;
    public Tuple<LinkedList<double>, LinkedList<double>> GraphData = new Tuple<LinkedList<double>, LinkedList<double>>(new LinkedList<double>(), new LinkedList<double>());
    public Timer GraphDataTimer;

    public LiveView()
    
        InitializeComponent();
        GraphDataTimer = new Timer(50);
        GraphDataTimer.Elapsed += GraphDataTimer_Elapsed;
    

    private void GraphDataTimer_Elapsed(object sender, ElapsedEventArgs e)
    
        Random random = new Random();
        if (GraphData.Item1.Count() > DataPointsToShow)
        
            GraphData.Item1.RemoveFirst();
            GraphData.Item2.RemoveFirst();
        

        GraphData.Item1.AddLast(random.NextDouble()*200);
        GraphData.Item2.AddLast(DateTime.Now.ToOADate());
        Dispatcher.Invoke(() =>
        
            linegraph.Plot(GraphData.Item1, GraphData.Item2);
        );

    

    private void Button_Click(object sender, RoutedEventArgs e)
    
        if (GraphDataTimer.Enabled)
        
            GraphDataTimer.Stop();
        
        else
        
            GraphDataTimer.Start();
        
    

基本上,它每 50 毫秒产生一个新值并将其添加到链表的末尾。如果总点数高于您要显示的数字,那么它还会删除第一个点,为您提供一个不断滚动的图表,其中最新数据位于顶部。

【讨论】:

【参考方案3】:

我设法改进了 Dmitry 建议的解决方案,使轴保持与图表的链接。

<Style x:Key="timeAxisStyle" TargetType="d3:PlotAxis">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="d3:PlotAxis">
                    <Grid>
                        <local:CustomAxis x:Name="PART_Axis" 
                             AxisOrientation="Binding AxisOrientation, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                             IsReversed="Binding IsReversed, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                             Ticks="Binding Ticks, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                             Foreground="Binding Foreground, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"/>
                        <ContentPresenter/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="IsTabStop" Value="False"/>
    </Style>
    <Style TargetType="d3:Chart">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="d3:Chart">
                    <Grid>
                        <d3:Figure x:Name="PART_figure" Margin="1"
                               PlotHeight="Binding PlotHeight, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               PlotWidth="Binding PlotWidth, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               PlotOriginX="Binding PlotOriginX, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               PlotOriginY="Binding PlotOriginY, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               IsXAxisReversed = "Binding IsXAxisReversed, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               IsYAxisReversed = "Binding IsXAxisReversed, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               IsAutoFitEnabled="Binding IsAutoFitEnabled, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               AspectRatio="Binding AspectRatio, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                               ExtraPadding="TemplateBinding BorderThickness"
                               Background="TemplateBinding Background">
                            <d3:MouseNavigation IsVerticalNavigationEnabled="TemplateBinding IsVerticalNavigationEnabled"
                                            IsHorizontalNavigationEnabled="TemplateBinding IsHorizontalNavigationEnabled"
                                            x:Name="PART_mouseNavigation"/>
                            <d3:KeyboardNavigation IsVerticalNavigationEnabled="TemplateBinding IsVerticalNavigationEnabled"
                                               IsHorizontalNavigationEnabled="TemplateBinding IsHorizontalNavigationEnabled"
                                               x:Name="PART_keyboardNavigation"/>
                            <d3:VerticalContentControl d3:Figure.Placement="Left"
                                                   Content="TemplateBinding LeftTitle"
                                                   VerticalAlignment="Center"
                                                   IsTabStop="False"/>
                            <d3:PlotAxis x:Name="PART_verticalAxis"
                                     d3:Figure.Placement="Left" 
                                     AxisOrientation="Left"
                                     IsReversed = "Binding IsYAxisReversed, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                                     Foreground="TemplateBinding Foreground"
                                     Style="StaticResource timeAxisStyle">
                                <d3:MouseNavigation IsHorizontalNavigationEnabled="False"/>
                            </d3:PlotAxis>
                            <d3:AxisGrid x:Name="PART_axisGrid"
                                     VerticalTicks="Binding Ticks,ElementName=PART_verticalAxis, Mode=OneWay"
                                     HorizontalTicks="Binding Ticks,ElementName=PART_horizontalAxis, Mode=OneWay"
                                     IsXAxisReversed = "Binding IsXAxisReversed, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                                     IsYAxisReversed = "Binding IsXAxisReversed, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                                     Stroke="TemplateBinding Foreground" Opacity="0.25"/>
                            <ContentControl d3:Figure.Placement="Top" 
                                        HorizontalAlignment="Center"
                                        FontSize="16"
                                        Content="TemplateBinding Title"
                                        Foreground="TemplateBinding Foreground"
                                        IsTabStop="False"/>
                            <ContentControl d3:Figure.Placement="Bottom" 
                                        HorizontalAlignment="Center"
                                        Content="TemplateBinding BottomTitle"
                                        Foreground="TemplateBinding Foreground"
                                        IsTabStop="False"/>
                            <d3:VerticalContentControl d3:Figure.Placement="Right"
                                                   Content="TemplateBinding RightTitle"
                                                   VerticalAlignment="Center"
                                                   IsTabStop="False"/>
                            <d3:PlotAxis x:Name="PART_horizontalAxis"
                                     d3:Figure.Placement="Bottom" 
                                     AxisOrientation="Bottom"
                                     IsReversed = "Binding IsXAxisReversed, Mode=TwoWay, RelativeSource=RelativeSource TemplatedParent"
                                     Foreground="TemplateBinding Foreground">
                                <d3:MouseNavigation IsVerticalNavigationEnabled="False"/>
                            </d3:PlotAxis>
                            <ContentPresenter/>
                            <Border BorderThickness="TemplateBinding BorderThickness"
                                BorderBrush="TemplateBinding Foreground" d3:Figure.Placement="Center"/>
                            <d3:Legend x:Name="PART_legend" 
                                   Foreground="Black" Content="TemplateBinding LegendContent"
                                   Visibility="TemplateBinding LegendVisibility"/>
                        </d3:Figure>
                        <Rectangle x:Name="FocusVisualElement" RadiusX="2" RadiusY="2" Stroke="#FF6DBDD1" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="IsTabStop" Value="False"/>
    </Style>

【讨论】:

以上是关于InteractiveDataDisplay.WPF 制作动态图表作为值记录器外观的主要内容,如果未能解决你的问题,请参考以下文章