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

问题描述 投票:0回答:3

我使用 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)不同步,不随数据移动。如何解决这个问题?

c# wpf wpf-controls customization dynamic-data-display
3个回答
5
投票

如何将轴移动到顶部?

图表布局模板在 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,则需要将自定义 标签提供程序 注入到 轴控件中。

为此,您可以创建从

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>

2
投票

我设法改进了 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>

1
投票

这是我想出的,它的边缘相当粗糙,但应该对你有所帮助。您的 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 毫秒产生一个新值并将其添加到链表的末尾。如果总点数高于您要显示的数量,那么它还会删除第一个点,为您提供一个不断滚动的图表,最新数据位于顶部。

© www.soinside.com 2019 - 2024. All rights reserved.