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 制作动态图表作为值记录器外观的主要内容,如果未能解决你的问题,请参考以下文章