之前需要做一个DataGrid的数据分析,大概就是选择N条数据后导出为图表,之前试了一个微软的图表库,但样子惨不忍睹,
今天下午空了下来,想起来这个图表,决定自己做个试试看,于是3小时成果,这是Git地址
先看效果图(其实是动态的,但GIF太麻烦了):
代码:
Xaml代码:
<UserControl x:Class="Custom_Bar.BarChartEx"
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:Custom_Bar"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="700">
<Grid TextElement.Foreground="White"
Background="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl},AncestorLevel=1},Path=Background}">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
<RowDefinition Height="12*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<TextBlock x:Name="_Title" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center"
Text="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl},AncestorLevel=1},Path=Title}"
Background="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl},AncestorLevel=1},Path=TitleBackground}"
Foreground="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl},AncestorLevel=1},Path=TitleForeground}"
FontSize="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl},AncestorLevel=1},Path=TitleFontSize}"/>
<StackPanel x:Name="_Content" Grid.Row="2" Margin="50,30,50,30" Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
<TextBlock x:Name="_Remark" Grid.Row="3" VerticalAlignment="Top" HorizontalAlignment="Center"
Text="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl},AncestorLevel=1},Path=Remark}"
Foreground="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl},AncestorLevel=1},Path=RemarkForeground}"
FontSize="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl},AncestorLevel=1},Path=RemarkFontSize}"
Margin="50,0,50,0"/>
</Grid>
</UserControl>
控件后台代码:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace Custom_Bar
{
/// <summary>
/// BarChartEx.xaml 的交互逻辑
/// </summary>
public partial class BarChartEx : UserControl
{
#region Tempelete DependencyProperty
/// <summary>
/// 标题
/// </summary>
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(BarChartEx), new PropertyMetadata(string.Empty));
/// <summary>
/// 备注
/// </summary>
public string Remark
{
get { return (string)GetValue(RemarkProperty); }
set { SetValue(RemarkProperty, value); }
}
public static readonly DependencyProperty RemarkProperty =
DependencyProperty.Register("Remark", typeof(string), typeof(BarChartEx), new PropertyMetadata(string.Empty));
/// <summary>
/// 标题背景色
/// </summary>
public Brush TitleBackground
{
get { return (Brush)GetValue(TitleBackgroundProperty); }
set { SetValue(TitleBackgroundProperty, value); }
}
public static readonly DependencyProperty TitleBackgroundProperty =
DependencyProperty.Register("TitleBackground", typeof(Brush), typeof(BarChartEx), new PropertyMetadata(Brushes.Transparent));
/// <summary>
/// 备注背景色
/// </summary>
public Brush RemarkBackground
{
get { return (Brush)GetValue(RemarkBackgroundProperty); }
set { SetValue(RemarkBackgroundProperty, value); }
}
public static readonly DependencyProperty RemarkBackgroundProperty =
DependencyProperty.Register("RemarkBackground", typeof(Brush), typeof(BarChartEx), new PropertyMetadata(Brushes.Transparent));
/// <summary>
/// 进度条背景色
/// </summary>
public Brush BarBackground
{
get { return (Brush)GetValue(BarBackgroundProperty); }
set { SetValue(BarBackgroundProperty, value); }
}
public static readonly DependencyProperty BarBackgroundProperty =
DependencyProperty.Register("BarBackground", typeof(Brush), typeof(BarChartEx), new PropertyMetadata((Brush)(new BrushConverter().ConvertFromString("#FF00BBB3"))));
/// <summary>
/// 标题前景色
/// </summary>
public Brush TitleForeground
{
get { return (Brush)GetValue(TitleForegroundProperty); }
set { SetValue(TitleForegroundProperty, value); }
}
public static readonly DependencyProperty TitleForegroundProperty =
DependencyProperty.Register("TitleForeground", typeof(Brush), typeof(BarChartEx), new PropertyMetadata(Brushes.White));
/// <summary>
/// 备注前景色
/// </summary>
public Brush RemarkForeground
{
get { return (Brush)GetValue(RemarkForegroundProperty); }
set { SetValue(RemarkForegroundProperty, value); }
}
public static readonly DependencyProperty RemarkForegroundProperty =
DependencyProperty.Register("RemarkForeground", typeof(Brush), typeof(BarChartEx), new PropertyMetadata(Brushes.White));
/// <summary>
/// x轴前景色
/// </summary>
public Brush XAxisForeground
{
get { return (Brush)GetValue(XAxisForegroundProperty); }
set { SetValue(XAxisForegroundProperty, value); }
}
public static readonly DependencyProperty XAxisForegroundProperty =
DependencyProperty.Register("XAxisForeground", typeof(Brush), typeof(BarChartEx), new PropertyMetadata(Brushes.White));
/// <summary>
/// 进度数据前景色
/// </summary>
public Brush BarDateForeground
{
get { return (Brush)GetValue(BarDateForegroundProperty); }
set { SetValue(BarDateForegroundProperty, value); }
}
public static readonly DependencyProperty BarDateForegroundProperty =
DependencyProperty.Register("BarDateForeground", typeof(Brush), typeof(BarChartEx), new PropertyMetadata(Brushes.White));
/// <summary>
/// 标题字号
/// </summary>
public double TitleFontSize
{
get { return (double)GetValue(TitleFontSizeProperty); }
set { SetValue(TitleFontSizeProperty, value); }
}
public static readonly DependencyProperty TitleFontSizeProperty =
DependencyProperty.Register("TitleFontSize", typeof(double), typeof(BarChartEx), new PropertyMetadata(26d));
/// <summary>
/// 备注字号
/// </summary>
public double RemarkFontSize
{
get { return (double)GetValue(RemarkFontSizeProperty); }
set { SetValue(RemarkFontSizeProperty, value); }
}
public static readonly DependencyProperty RemarkFontSizeProperty =
DependencyProperty.Register("RemarkFontSize", typeof(double), typeof(BarChartEx), new PropertyMetadata(10d));
/// <summary>
/// x轴字号
/// </summary>
public double XAxisFontSize
{
get { return (double)GetValue(XAxisFontSizeProperty); }
set { SetValue(XAxisFontSizeProperty, value); }
}
public static readonly DependencyProperty XAxisFontSizeProperty =
DependencyProperty.Register("XAxisFontSize", typeof(double), typeof(BarChartEx), new PropertyMetadata(12d));
/// <summary>
/// 进度数据字号
/// </summary>
public double BarDateFontSize
{
get { return (double)GetValue(BarDateFontSizeProperty); }
set { SetValue(BarDateFontSizeProperty, value); }
}
public static readonly DependencyProperty BarDateFontSizeProperty =
DependencyProperty.Register("BarDateFontSize", typeof(double), typeof(BarChartEx), new PropertyMetadata(8d));
/// <summary>
/// 进度条宽度
/// </summary>
public double BarWidth
{
get { return (double)GetValue(BarWidthProperty); }
set { SetValue(BarWidthProperty, value); }
}
public static readonly DependencyProperty BarWidthProperty =
DependencyProperty.Register("BarWidth", typeof(double), typeof(BarChartEx), new PropertyMetadata(20d));
#endregion
#region Data Property
/// <summary>
/// X轴关联属性名
/// </summary>
public string XValuePath
{
get { return (string)GetValue(XValuePathProperty); }
set { SetValue(XValuePathProperty, value); }
}
public static readonly DependencyProperty XValuePathProperty =
DependencyProperty.Register("XValuePath", typeof(string), typeof(BarChartEx), new PropertyMetadata(string.Empty, (s, e) =>
{
BarChartEx chartEx = s as BarChartEx;
if (chartEx.YValuePath != string.Empty && chartEx.dataSource != null)
{
chartEx._Content.Children.Clear();
CreateBarChart(chartEx);
}
}));
/// <summary>
/// Y轴关联属性名
/// </summary>
public string YValuePath
{
get { return (string)GetValue(YValuePathProperty); }
set { SetValue(YValuePathProperty, value); }
}
public static readonly DependencyProperty YValuePathProperty =
DependencyProperty.Register("YValuePath", typeof(string), typeof(BarChartEx), new PropertyMetadata(string.Empty, (s, e) =>
{
BarChartEx chartEx = s as BarChartEx;
Calulate_Data2Height_Value(chartEx);
if (chartEx.XValuePath != string.Empty && chartEx.dataSource != null)
{
chartEx._Content.Children.Clear();
CreateBarChart(chartEx);
}
}));
/// <summary>
/// 数据源
/// </summary>
private dynamic dataSource;
public dynamic DataSource
{
get { return dataSource; }
set
{
dataSource = value;
if (XValuePath != string.Empty && YValuePath != string.Empty)
{
_Content.Children.Clear();
CreateBarChart(this);
}
}
}
/// <summary>
/// Y轴的数据转化成相应的条图的高度的比例
/// </summary>
private int _data2BarHeight { get; set; } = 2;
#endregion
public BarChartEx()
{
InitializeComponent();
//设置一个默认的背景色
Background = (Brush)(new BrushConverter().ConvertFromString("#FF5DC6C6"));
}
#region OtherMethods
/// <summary>
/// 创建一条数据(数据值,条图,相应X轴的数据)
/// </summary>
/// <param name="date">Y轴数据</param>
/// <param name="x">X轴数据</param>
/// <returns></returns>
private StackPanel GetBar(double date, dynamic x)
{
//进度条容器
StackPanel _item = new StackPanel
{
Orientation = Orientation.Vertical,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom,
Margin = new Thickness(0, 0, 10, 0)
};
//数据部分
TextBlock _data = new TextBlock()
{
Text = date.ToString("f2"),
Margin = new Thickness(0, 0, 0, 5),
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center,
Foreground = BarDateForeground,
FontSize = BarDateFontSize
};
//进度条
Border _bar = new Border
{
Background = BarBackground,
Width = BarWidth,
Height = date * _data2BarHeight,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom
};
//添加进度条的加载动画
_bar.Loaded += (s, e) =>
{
DoubleAnimation animation = new DoubleAnimation
{
From = 0,
To = date * _data2BarHeight,
Duration = TimeSpan.FromSeconds(1)
};
_bar.BeginAnimation(Border.HeightProperty, animation);
};
_item.Children.Add(_data);
_item.Children.Add(_bar);
//x轴的数据
TextBlock _x = new TextBlock()
{
Text = x + "",
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center,
Foreground = XAxisForeground,
FontSize = XAxisFontSize
};
_x.Loaded += (s, e) =>
{
_x.RenderTransformOrigin = new Point(1, 1);
_x.RenderTransform = new RotateTransform();
var rt = (RotateTransform)_x.RenderTransform;
rt.BeginAnimation(RotateTransform.AngleProperty, new DoubleAnimation(0, -45, TimeSpan.FromSeconds(2)));
};
//一列容器(包括x轴)
StackPanel _Item = new StackPanel
{
Orientation = Orientation.Vertical,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom,
};
_Item.Children.Add(_item);
_Item.Children.Add(_x);
return _Item;
}
/// <summary>
/// 创建图表
/// </summary>
/// <param name="chartEx"></param>
private static void CreateBarChart(BarChartEx chartEx)
{
foreach (var item in chartEx.dataSource)
{
System.Reflection.PropertyInfo[] properties = item.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
dynamic xValue = 0, yValue = 0;
xValue = item.GetType().GetProperty(chartEx.XValuePath).GetValue(item);
yValue = item.GetType().GetProperty(chartEx.YValuePath).GetValue(item);
var bar = chartEx.GetBar(yValue, xValue);
bar.Height = chartEx._Content.Height;
chartEx._Content.Children.Add(bar);
}
}
/// <summary>
/// 计算Y的数据值和条图高像素设置的比例
/// </summary>
/// <param name="chartEx"></param>
private static void Calulate_Data2Height_Value(BarChartEx chartEx)
{
if (chartEx.dataSource != null)
{
double maxValue = 0;
//遍历取得数据源中最大的Y值
foreach (var item in chartEx.dataSource)
{
dynamic temp = item.GetType().GetProperty(chartEx.YValuePath).GetValue(item);
try
{
double flag = temp;
maxValue = maxValue > flag ? maxValue : flag;
}
catch (Exception)
{
MessageBox.Show("Y数据类型错误");
}
}
//将条图区域的高和Y值的比例作为换算比例
//50在这用作Y数据Text和X轴数据Text的控件高总和的估值 (暂时没必要精确)
chartEx._data2BarHeight = int.Parse(((chartEx._Content.ActualHeight - 50) / maxValue).ToString());
}
}
#endregion
}
}
应用:
<Window x:Class="Custom_Bar.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:Custom_Bar"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800" Background="Gray">
<Grid>
<local:BarChartEx x:Name="MyTable" Height="400" Width="600" Title="this is a title" Remark="Remark:这里是备注这里是备注这里是备注"/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
namespace Custom_Bar
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Bar> _bar = new List<Bar>
{
new Bar() { BarName = "Rajesh", Value = 80 },
new Bar() { BarName = "Suresh", Value = 60 },
new Bar() { BarName = "Dan", Value = 40 },
new Bar() { BarName = "Sevenx", Value = 67 },
new Bar() { BarName = "Patel", Value = 15 },
new Bar() { BarName = "Joe", Value = 70 },
new Bar() { BarName = "Bill", Value = 90 },
new Bar() { BarName = "Vlad", Value = 23 },
new Bar() { BarName = "Steve", Value = 12 },
new Bar() { BarName = "Pritam", Value = 100 },
new Bar() { BarName = "Genis", Value = 54 },
new Bar() { BarName = "Ponnan", Value = 84 },
new Bar() { BarName = "Mathew", Value = 43 }
};
//数据源是一个动态集合
MyTable.YValuePath = "Data";
MyTable.XValuePath = "Name";
MyTable.DataSource = new RecordCollection(_bar);
//数据源是一个列表
//MyTable.YValuePath = "Value";
//MyTable.XValuePath = "BarName";
//MyTable.DataSource = _bar;
}
}
class RecordCollection : ObservableCollection<Record>
{
public RecordCollection(List<Bar> barvalues)
{
Random rand = new Random();
BrushCollection brushcoll = new BrushCollection();
foreach (Bar barval in barvalues)
{
int num = rand.Next(brushcoll.Count / 3);
Add(new Record(barval.Value, brushcoll[num], barval.BarName));
}
}
}
class BrushCollection : ObservableCollection<Brush>
{
public BrushCollection()
{
Type _brush = typeof(Brushes);
PropertyInfo[] props = _brush.GetProperties();
foreach (PropertyInfo prop in props)
{
Brush _color = (Brush)prop.GetValue(null, null);
if (_color != Brushes.LightSteelBlue && _color != Brushes.White &&
_color != Brushes.WhiteSmoke && _color != Brushes.LightCyan &&
_color != Brushes.LightYellow && _color != Brushes.Linen)
Add(_color);
}
}
}
class Bar
{
public string BarName { set; get; }
public int Value { set; get; }
}
class Record : INotifyPropertyChanged
{
public Brush Color { set; get; }
public string Name { set; get; }
private int _data;
public int Data
{
set
{
if (_data != value)
{
_data = value;
}
}
get
{
return _data;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Record(int value, Brush color, string name)
{
Data = value;
Color = color;
Name = name;
}
protected void PropertyOnChange(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
}