How to made multiple LineChart in WPF Dynamic Data Display? - c#

I have 3 collection in c# (wpf application). I need to show them a line chart graph. I researched too much websites and i only have dynamic data display d3. so i tried to make a chart as below in wpf.
i found a code from stackoverflow but i couldnt make it as i thought. i can only show one line in the field and it can only show as below.
and my another problem is horizontal axis. How can i make horizontal values as string or as [10-2016]. Here is codes i found.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:d3="clr-namespace:Microsoft.Research.DynamicDataDisplay;assembly=DynamicDataDisplay"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<d3:ChartPlotter>
<d3:LineGraph DataSource="{Binding Data}"></d3:LineGraph>
</d3:ChartPlotter>
</Grid>
</Window>
MainWindow.xaml.cs
MyViewModel viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new MyViewModel();
DataContext = viewModel;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
double[] my_array = new double[10];
for (int i = 0; i < my_array.Length; i++)
{
my_array[i] = Math.Sin(i)*3;
viewModel.Data.Collection.Add(new Point(i, my_array[i]));
}
}
MyViewModel.cs
public class MyViewModel
{
public ObservableDataSource<Point> Data { get; set; }
public MyViewModel()
{
Data = new ObservableDataSource<Point>();
}
}
Is there any easy way to make chart as upper picture. I dont know anything about wpf charts and i dont have any budget to pay wpf chart libraries. I hope anyone can help me.

I would recommend you use Live Charts.
Each line on the graph is represented by a 'LineSeries' and it is designed to be MVVM friendly. It's 100% free, and you can install using NuGet.
Live Charts
How to install
Example:
Xaml:
<Grid>
<lvc:CartesianChart Series="{Binding SeriesCollection}" LegendLocation="Right" >
<lvc:CartesianChart.AxisY>
<lvc:Axis Title="Sales" LabelFormatter="{Binding YFormatter}"></lvc:Axis>
</lvc:CartesianChart.AxisY>
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Month" Labels="{Binding Labels}"></lvc:Axis>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
</Grid>
ViewModel:
public class ViewModel
{
public SeriesCollection SeriesCollection { get; set; }
public string[] Labels { get; set; }
public Func<double, string> YFormatter { get; set; }
public ViewModel()
{
SeriesCollection = new SeriesCollection
{
new LineSeries
{
Title = "Series 1",
Values = new ChartValues<double> { 4, 6, 5, 2 ,4 }
},
new LineSeries
{
Title = "Series 2",
Values = new ChartValues<double> { 6, 7, 3, 4 ,6 },
PointGeometry = null
},
new LineSeries
{
Title = "Series 3",
Values = new ChartValues<double> { 4,2,7,2,7 },
PointGeometry = DefaultGeometries.Square,
PointGeometrySize = 15
}
};
Labels = new[] {"Jan", "Feb", "Mar", "Apr", "May"};
YFormatter = value => value.ToString("C");
//modifying the series collection will animate and update the chart
SeriesCollection.Add(new LineSeries
{
Title = "Series 4",
Values = new ChartValues<double> {5, 3, 2, 4},
LineSmoothness = 0, //0: straight lines, 1: really smooth lines
PointGeometry = Geometry.Parse("m 25 70.36218 20 -28 -20 22 -8 -6 z"),
PointGeometrySize = 50,
PointForeground = Brushes.Gray
});
//modifying any series values will also animate and update the chart
SeriesCollection[3].Values.Add(5d);
}
}
Output:

Related

WPF with LiveCharts add series with DataBinding at runtime

I have a project where I have a charting area.
I have choosen LiveCharts ( http://lvcharts.net ) for that matter.
So far so good, the charting works with databinding when added in xaml:
<UserControl x:Class="Work_Task_Planner_Sheduler.Views.TeamTaskProgressChartView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Work_Task_Planner_Sheduler.Views"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<lvc:CartesianChart x:Name="TeamTaskProgressChartComponent">
<lvc:CartesianChart.Series>
<lvc:LineSeries Title="Julian" Values="{Binding Taskprogress_julian}" />
</lvc:CartesianChart.Series>
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Time"></lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis Title="TaskProgress"></lvc:Axis>
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
</Grid>
</UserControl>
The issue is: The Line series is hardcoded in xaml. Who knows if the team gets bigger/smaller over time or the team members change. Therefore I want to create the line series at runtime.
This is what I have so far, unfortunately the series stays empty:
CartesianChart chart = this.TeamTaskProgressChartComponent;
foreach(string user in TeamMembers)
{
LineSeries lineseries = new LineSeries();
string title = user.Split('.')[0];
lineseries.Title = title;
Binding databinding = new Binding();
databinding.Source =
Datarefresh.mainWindow.mainViewModel.TeamTaskProgressChartViewModel.TaskProgressCounts;
DependencyProperty LineSeriesProperty = DependencyProperty.Register(title+"Property", typeof(int), typeof(LineSeries), new PropertyMetadata(0));
lineseries.SetBinding(LineSeriesProperty, databinding);
chart.series.Add(lineseries);
}
You have to set the source for the binding.
CartesianChart chart = new CartesianChart();
foreach(string user in TeamMembers)
{
LineSeries lineseries = new LineSeries();
string title = user.Split('.')[0];
lineseries.Title = title;
Binding databinding = new Binding("Taskprogress_"+title);
//===========Set source here before setting the binding to the lineseries===========
databinding.Source = "your datasource";
// the following line is pseudocode derived from Microsoft reference
// https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-create- a-binding-in-code?view=netframeworkdesktop-4.8
lineseries.SetBinding(LineSeries.Values, databinding);
}
Actually the Process is quite Simple:
Create a Series collation. IMPORTANT: The series collection must be initialized in the constructor so that it is ready when the chart loads:
public class TeamTicketResolveChartViewModel
{
public SeriesCollection ChartSeries { get; set; }
public string[] TicketResolveLabels { get; set; }
public TeamTicketResolveChartViewModel()
{
ChartSeries = new SeriesCollection();
foreach (string user in TeamMembers.ServiceDesk)
{
LineSeries lineseries = new LineSeries();
string title = user.Split('.')[0];
lineseries.Title = title;
ChartSeries.Add(lineseries);
}
}
// other functions
}
In xaml, bind the chart to the series Collection:
<lvc:CartesianChart x:Name="TeamTicketResolveChartComponent" Series="{Binding ChartSeries}" />
Now updating the Chart Data will work as intended:
public void LoadChartData2(List<List<(DateTime time, int resolved)>> input)
{
for (int i = 0; i < input.Count; i++)
{
ChartSeries[i].Values = new ChartValues<int>(input[i].Select(c => c.resolved));
}
DateTime[] dates = input[0].Select(c => c.time).ToArray();
List<string> labels = new List<string>();
foreach (DateTime time in dates) labels.Add(time.ToString("HH:mm:ss"));
TicketResolveLabels = labels.ToArray();
}
public void AddChartPoint(List<List<(DateTime time, int resolved)>> input)
{
// Chartseries[i].values.Add()
}

Use PieChart with LiveCharts and WPF .NET Core

I use chart plugin for WPF:
https://lvcharts.net
In xaml I have:
<Window x:Class="GoogleDriveManager.WPF.ChartWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GoogleDriveManager.WPF" xmlns:wpf="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="Chart statistics" Height="450" Width="800">
<Grid>
<wpf:PieChart LegendLocation="Bottom" Series="{Binding Items}"/>
</Grid>
</Window>
and code behind:
public ChartWindow()
{
InitializeComponent();
DataContext = this;
Items = new SeriesCollection();
ContentRendered += (s, ev) =>
{
Dictionary<string, List<string>> data = GetData(...);
foreach (var item in data)
{
ISeriesView series = new PieSeries(new { title = item.Key });
IChartValues values = new ChartValues<string>(item.Value);
series.Values = values;
Items.Add(series);
}
};
}
public SeriesCollection Items { get; }
But seems that the window is empty.
You should transfer string values to double
Use PieSeries properties Title to assign title
I create a simple case and this code works:
ContentRendered += (s, ev) =>
{
Dictionary<string, List<string>> data = new Dictionary<string, List<string>>(){
{"AAA", new List<string>(){"1","2"}},
{"BBB", new List<string>(){"3","4"}}
};
foreach (var item in data)
{
var curValues = item.Value.Select(x => double.Parse(x)).ToList();
ISeriesView series = new PieSeries{
Title = item.Key,
Values = new ChartValues<double>(curValues),
DataLabels = true
};
Items.Add(series);
}
};

How can I remove the axes from an Oxplot graph but retain zoom and pan functionality?

I can remove axes using IsAxisVisible = false like the answers here, but when I do so can no longer pan or zoom the graph.
Example code where the graph doesn't pan (using Oxyplot 2.0):
public class MainViewModel
{
public MainViewModel()
{
var tmp = new PlotModel { Title = "Simple example", Subtitle = "using OxyPlot" };
tmp.Axes.Add(new LinearAxis()
{
Position = AxisPosition.Bottom,
IsAxisVisible = false
});
tmp.Axes.Add(new LinearAxis()
{
Position = AxisPosition.Left,
IsAxisVisible = false
});
var series1 = new LineSeries { Title = "Series 1", MarkerType = MarkerType.Circle };
series1.Points.Add(new DataPoint(0, 0));
series1.Points.Add(new DataPoint(10, 18));
series1.Points.Add(new DataPoint(20, 12));
series1.Points.Add(new DataPoint(30, 8));
series1.Points.Add(new DataPoint(40, 15));
tmp.Series.Add(series1);
this.Model = tmp;
}
public PlotModel Model { get; private set; }
}
Edit:
xaml
<Window x:Class="SimpleDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
xmlns:simpleDemo="clr-namespace:SimpleDemo"
Title="OxyPlot SimpleDemo" Height="480" Width="640">
<Window.DataContext>
<simpleDemo:MainViewModel />
</Window.DataContext>
<Grid>
<oxy:PlotView Model="{Binding Model}" />
</Grid>
</Window>
xaml.cs
public partial class MainWindow
{
public MainWindow()
{
this.InitializeComponent();
}
}
As mentioned in comment earlier, this behavior seems to be happening with Oxyplot 2.0, while 1.0 does allow Zoom/Pan even when the Axis is invisible.
An alternative approach would be to set the AxislineColor,TextColor,and TicklineColor to OxyColors.Transparent.
tmp.Axes.Add(new LinearAxis()
{
Position = AxisPosition.Bottom,
IsZoomEnabled = true,
IsPanEnabled = true,
AxislineColor = OxyColors.Transparent,
TextColor = OxyColors.Transparent,
TicklineColor = OxyColors.Transparent
}); ; ;
tmp.Axes.Add(new LinearAxis()
{
Position = AxisPosition.Left,
IsZoomEnabled = true,
IsPanEnabled = true,
AxislineColor = OxyColors.Transparent,
TextColor = OxyColors.Transparent,
TicklineColor = OxyColors.Transparent
});
This would allow you to Zoom/Pan without displaying the Axis.
The solution I've settled on (at least until the IsAxisVisible = false issue described in the original question is fixed) is to implement Anu's answer, with the addition of:
Model.PlotMargins = new OxyThickness(0);
To hide the axis thickness.

How can I stop the columns resizing on scroll in a WPF DataGrid with row virtualization turned on?

I have a WPF DataGrid, bound to a DataTable which may contain a variable number of columns. This DataTable can change its schema and data at runtime, whenever the user chooses a new data file to load into the app (in the example below, this is simulated by clicking the Load Data button).
I've set the column width to Auto so that columns automatically resize to fit their header text or the longest piece of row content in the column (whichever is larger). Here's the MainWindow.xaml from my example:
<Window x:Class="analog.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:analog"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="600">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="loadData" Grid.Row="0" Margin="0" Content="Load Data" Click="loadData_Click" VerticalAlignment="Top" />
<DataGrid x:Name="dataGrid" Grid.Row="1" Margin="0" IsReadOnly="True" CanUserAddRows="False" ColumnWidth="Auto" SelectionUnit="Cell" />
</Grid>
</Window>
And here's MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void loadData_Click(object sender, RoutedEventArgs e)
{
var data = new DataTable();
data.Columns.Add("A", typeof(string));
data.Columns.Add("B", typeof(string));
data.Columns.Add("C", typeof(string));
Enumerable.Range(1, 50).ToList().ForEach(i => {
var row = data.NewRow();
row["A"] = "aaa";
row["B"] = "bbb";
row["C"] = "ccc";
data.Rows.Add(row);
});
var longRow = data.NewRow();
longRow["A"] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
longRow["B"] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
longRow["C"] = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
data.Rows.Add(longRow);
dataGrid.ItemsSource = data.DefaultView;
}
}
This works perfectly, except that because the DataGrid rows are virtual (i.e. rendered on demand when the list is scrolled, as opposed to upfront), the columns can only automatically resize to the content which is currently being displayed.
However, when you then scroll down to the bottom of the list, the column widths suddenly and dramatically change as the longer cell content scrolls into view, making for a really horrible, disorientating user experience.
Turning off row virtualisation isn't an option (e.g. EnableRowVirtualization="False"), as I'm loading in some large data sets and performance is unusably bad without it.
I understand the limitation on sizing when using virtualisation and I'm quite happy with that behaviour. It's perfectly OK if the columns just stay at the widths they are set to when the grid is initially rendered, but I just can't find a way to do it!
I've tried some fairly horrible hacks, including this one where I loop over the columns after the initial render and programmatically set the widths in pixels to whatever size they are currently rendered at:
foreach (DataGridColumn column in dataGrid.Columns)
{
column.Width = new DataGridLength(column.ActualWidth, DataGridLengthUnitType.Pixel);
}
I'm currently calling this manually, by putting it in a button click handler and clicking the button after the initial data has rendered—but this has no effect whatsoever, and the resize still happens when I get down to the longer values.
So, how can I stop the columns resizing when I scroll the DataGrid?
Another option (not exactly as a solution for the question, but similar situations, especially when EnableColumnVirtualization is true), is to simply set the MaxWidth property of columns. An example:
public MainWindow()
{
InitializeComponent();
DataTable tab = new DataTable();
for (int i = 0; i < 1000; i++)
tab.Columns.Add("A" + i.ToString());
for (int i = 0; i < 1000; i++)
{
DataRow r = tab.NewRow();
for (int j = 0; j < 1000; j++)
r[j] = "something " + (i * i * j * j).ToString();
tab.Rows.Add(r);
}
DataGrid dg = new DataGrid() { EnableColumnVirtualization = true, EnableRowVirtualization = true };
this.Content = dg;
dg.ItemsSource = tab.AsDataView();
dg.AutoGeneratingColumn += Dg_AutoGeneratingColumn;
}
private void Dg_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
e.Column.MaxWidth = 100;
}
Setting the Width explicitly should work. Make sure that you do this once the DataGrid has been loaded.
Please refer to the following sample code which works for me.
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<DgItem> items = new List<DgItem>()
{
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abc", B = "abc" },
new DgItem() { A = "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc", B = "abc" }
};
dg.ItemsSource = items;
}
private void dg_Loaded(object sender, RoutedEventArgs e)
{
foreach (DataGridColumn column in dg.Columns)
{
column.Width = new DataGridLength(column.ActualWidth, DataGridLengthUnitType.Pixel);
}
}
}
MainWindow.xaml:
<DataGrid x:Name="dg" AutoGenerateColumns="False" Height="100"
Loaded="dg_Loaded">
<DataGrid.Columns>
<DataGridTextColumn Header="1" Binding="{Binding A}" />
<DataGridTextColumn Header="2" Binding="{Binding B}" />
</DataGrid.Columns>
</DataGrid>

dynamicdatadisplay (wpf) how to make a string marker?

I'm using dynamicdatadisplay library for WPF! not SilverLight
I've finish my backend codding already and stuck with graph.
I have an X-Axis that is a DateTime (hours).
Also I Have an Y-Axis that is Decimal.
But I can't find how to put between this axes custom marker (or whatever) which will be a dynamic string.
For example in 10 hours 03.11.2013 (x-axis) the price of the Stock was 120$ (y-axis) but in graph (in the cross of the axes) it must be a string "Volume: 50" which will show how much stocks was sell.
How to make it in code? Please give me some examples or advice.
Now my project look's like this:
========= XAML ==========
<d3:ChartPlotter Name="plotter" Margin="20,20,20,70">
<d3:ChartPlotter.HorizontalAxis>
<d3:HorizontalDateTimeAxis Name="xAxis"/>
</d3:ChartPlotter.HorizontalAxis>
<d3:ChartPlotter.VerticalAxis>
<d3:VerticalIntegerAxis Name="yAxis"/>
</d3:ChartPlotter.VerticalAxis>
</d3:ChartPlotter>
<Button Content="Button" HorizontalAlignment="Left" Margin="254,364,0,0" VerticalAlignment="Top" Width="145" Height="46" Click="Button_Click_1"/>
</Grid>
=============================================
================ MainWindow.cs =================
private void Button_Click_1(object sender, RoutedEventArgs e)
{
List<DataForChart> dataForChart = new List<DataForChart>();
dataForChart.Add(new DataForChart(new DateTime(2013, 11, 03, 22, 10, 0), 45));
dataForChart.Add(new DataForChart(new DateTime(2013, 11, 03, 22, 20, 0), 48));
dataForChart.Add(new DataForChart(new DateTime(2013, 11, 03, 22, 30, 0), 24));
DateTime[] dates = new DateTime[dataForChart.Count];
int[] price = new int[dataForChart.Count];
for (int i = 0; i < dataForChart.Count; ++i)
{
dates[i] = dataForChart[i].date;
price[i] = dataForChart[i].price;
}
var datesDataSource = new EnumerableDataSource<DateTime>(dates);
datesDataSource.SetXMapping(x => xAxis.ConvertToDouble(x));
var numberOpenDataSource = new EnumerableDataSource<int>(price);
numberOpenDataSource.SetYMapping(y => y);
CompositeDataSource compositeDataSource1 = new
CompositeDataSource(datesDataSource, numberOpenDataSource);
plotter.AddLineGraph(compositeDataSource1,
new Pen(Brushes.Blue, 2),
new CirclePointMarker { Size = 10.0, Fill = Brushes.Blue },
new PenDescription("Price Chart"));
plotter.Viewport.FitToView();
}
public class DataForChart
{
public DateTime date;
public int price;
public DataForChart(DateTime Date, int Price)
{
date = Date;
price = Price;
}
}
// Create a custom marker control as follows
// CustomMarker.xaml:
<d3:ViewportUIContainer x:Class="MyNamespace.CustomMarker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:d3="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts;assembly=DynamicDataDisplay"
mc:Ignorable="d" >
<Grid>
<TextBlock Name="TxtBlk1" Text="Some Text" />
</Grid>
</d3:ViewportUIContainer>
// Marker control code behind CustomMarker.cs
using System;
using System.Windows;
using System.Windows.Media;
namespace MyNamespace
{
public partial class CustomMarker : Microsoft.Research.DynamicDataDisplay.Charts.ViewportUIContainer
{
public CustomMarker()
{
InitializeComponent();
}
public CustomMarker(Point position, string text, Color color) : this()
{
Position = position;
TxtBlk1.Text = text;
TxtBlk1.Foreground = new SolidColorBrush(color);
}
}
}
//In your mainwindow.cs
var position = new Point(x value, y value);
plotter.Children.Add(new CustomMarker(position, "Text", Colors.Black));

Categories