Reputation: 979
I've using Microsoft InteractiveDataDisplay.WPF (former DynamicDataDisplay) to visualize real time data (about 2-3 seconds). This code 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);
}
}
with this 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>
Give this view: But I want following custom graph:
Any ideas how to did it? Thanks!
Update 1 (using Kevin Ross solution):
Update 2 (using Dmitry Voytsekhovskiy solution):
But the time axis (Y) not synchronize and not move with data. how to fix this?
Upvotes: 0
Views: 4694
Reputation: 126
The chart layout template is defined in Themes/Generic.xaml as a style for d3:Chart
.
You can create a custom style where the horizontal axis is located at the top (d3:Figure.Placement="Top"
) and has correct orientation (AxisOrientation="Top"
). For instance,
<d3:PlotAxis x:Name="PART_horizontalAxis"
d3:Figure.Placement="Top"
AxisOrientation="Top"
Foreground="{TemplateBinding Foreground}">
<d3:MouseNavigation IsVerticalNavigationEnabled="False"/>
</d3:PlotAxis>
For example, if values along y are actually hours since certain time moment, and you want to show axis ticks as HH:mm, you need to inject a custom label provider into an axis control.
To do that you can create new axis class derived from Axis
and pass custom label provider to the base constructor:
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())
{
}
}
Now return to the custom Chart template and change for the vertical axis its type from PlotAxis
to CustomAxis
(note that you might need to change type prefix):
<d3:CustomAxis x:Name="PART_verticalAxis"
d3:Figure.Placement="Left"
AxisOrientation="Left"
Foreground="{TemplateBinding Foreground}">
<d3:MouseNavigation IsHorizontalNavigationEnabled="False"/>
</d3:CustomAxis>
If we do the described steps for the LineGraphSample and run it, we get the following:
Finally, the custom chart 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}}"
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>
Upvotes: 5
Reputation: 7215
This is what I came up with, it's quite rough round the edges but should help you on your way. Your view XAML stays largely the same I've just added a button to start and stop things
<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>
Your code behind then becomes
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();
}
}
}
Basically what it does it come up with a new value every 50 milliseconds and adds it to the end of the linked list. If the total number of points is above the number you want to display then it also removes the first one giving you a constantly scrolling graph with the most recent data at the top.
Upvotes: 1
Reputation: 51
I managed to improve the solution suggested by Dmitry so that the axis remains linked to the graph.
<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>
Upvotes: 2