自定义RectangleChart图形控件--分享

  1.前言
  最近有好一阵子没有写文章了,这几天无聊写了一个主要用于MVVM绑定的自定义RectangleChart柱状图形控件,主要模仿Chart图形中的数据显示。这里跟大家分享下自定义RectangleChart柱状图形控件的思路和心得。

  

  2.功能:
  以下图片为自定义RectangleChart柱状体控件的详细功能。
     刻度表   :通过刻度表可以让整个控件的图形数据显示更加直观。
     刻度列表:显示图形中的所有刻度。
     刻度值   :标记整个图形中各个刻度的进制位(如图形中所示,进制位为10)。
     柱状体   :数据的柱状图形表示。
     柱状体值:具体的数据值。
     数据列表:整个图形中的所有数据。
      
  3.思路:
  这种简单的图形控件的思路一般是这样的。首先我们会考虑到,我们需要画一个刻度表(也就是表格),然后再画上刻度列表(也就是右侧0-100的数据),然后根据数据列表画上相应的柱状体。这个是我们起初的思路。然后我们需要根据一定的计算后,把结果画在Canvas面板中的相应位置上。
   根据初始的思路我们想到我们需要定义一个依赖属性(DataSource 类型为List<ChartData>),ChartData为自定义的一个数据类型。然后我们需要根据接收到的DataSource的值进行计算后绘图。绘图前我们需要实现的功能就是画刻度列表,刻度列表不可以随便画,必须计算出接近数据结合(DataSource值)的值。也就是计算出最接近DataSource中的最大值和最小指,比如图形中的刻度列表最大值为100,最小值为0。计算的思路是找出最大值然后去。
  
  
   计算前我们需要定义一些内部使用的变量,其具体代码如下:  
View Code
private string _lineColor = "#D3E5FF";      //刻度列表线颜色

        private double ChartMaxYValue = 100;        //Y轴最大值
        private double ChartMaxXValue { getset; } //X轴最大值
       
        private int leftMargin = 100;       //面板左边框
        private int rightMargin = 100;      //面板右边框

        private int topMargin = 20;         //面板上边框
        private int bottomMargin = 20;      //面板下边框

        private int leftChartMargin = 20;   //具体图形的左边框
        private int rightChartMargin = 20;  //具体图形的右边框

        private int ySize = 40;             //Y轴线间距
        private int xSize = 50;             //X轴线间距

        private int startY = 20;            //Y轴起点
        private int endY = 460;             //Y轴结束点

        private int startX = 0;             //X轴起点
        private int endX = 100;             //Y轴起点

        private double yStepSize = 0.8;     //Y轴的进位制
        private int chartWidth = 40;        //图形宽度
   步骤一:计算的代码如下:
  
View Code
/// <summary>
        
/// 取DataSource最大值
        
/// </summary>
        public void MeasuneValue()
        {
            if (DataSource != null)
            {
                double max = 0;
                foreach(var data in DataSource)
                {
                    if (data.ChartValue > max)
                    {
                        max = data.ChartValue;
                    }
                }

                ChartMaxYValue = GetMaxYValue(max);
            }            
        }
        
        /// <summary>
        
/// 获取最大进位制(如60返回100 4返回10)
        
/// </summary>
        
/// <param name="max">最大值</param>
        
/// <returns>最大进位制</returns>
        public double GetMaxYValue(double max)
        {
            int valueTime = (int)Math.Log10(max);    //计算出最大的进位制

            return Math.Pow(10, valueTime+1);
        }
   步骤二:然后我们就可以进行刻度表了(也就是画表格了),从上面我们已经计算得出的Y轴最大值和Y轴最小指(这里只是简单开发,所以只考虑最大值,也就是说暂时还没考虑负值的图形),我们就可以进行表格,绘画的思路就是记录下线条的位置然后添加到Canvas面板中,画X轴、Y轴线条具体代码如下:
  
View Code
        /// <summary>
        
/// 画Y轴的线条
        
/// </summary>
        
/// <param name="container"></param>
        public void DrawYLine(Canvas container)
        {
            var x2Value = leftMargin + (DataSource.Count+1) * xSize;
            for (int i = 0; i < 12; i++)
            {
                container.Children.Add(new Line { X1 = leftMargin, Y1 = topMargin + (i * ySize), X2 = x2Value, Y2 = topMargin + (i * ySize), Stroke = new SolidColorBrush(ChartLineColor) });
            }
        }

        /// <summary>
        
/// 画X轴的线条
        
/// </summary>
        
/// <param name="container"></param>
        public void DratXLine(Canvas container)
        {            
            for (int i = 0; i < DataSource.Count+2;i++ )
            {
                var xValue = leftMargin + (i * xSize);
                container.Children.Add(new Line { X1 = xValue, Y1 = startY, X2 = xValue, Y2 = endY, Stroke = new SolidColorBrush(ChartLineColor) });
            }
        }
  步骤三:我们需要把相应的刻度值画到表格的Y轴上:具体代码如下
  
View Code
        /// <summary>
        
/// 画Y轴上的数值
        
/// </summary>
        
/// <param name="container"></param>
        public void DrawYValue(Canvas container)
        {
            for (int i = 0; i <=10; i++)
            {
                double value = i * ChartMaxYValue / 10;

                TextBlock t = new TextBlock {Text = value.ToString(),Foreground = ValueForeground};
                t.Width = 80;
                t.TextAlignment = TextAlignment.Right;
                Canvas.SetLeft(t,10);
                Canvas.SetTop(t,50+(10-i)*ySize);
                MainContainer.Children.Add(t);
            }            
        }
  步骤四:画柱状体:具体代码如下
  
View Code
        /// <summary>
        
/// 画图形
        
/// </summary>
        
/// <param name="container"></param>
        public void DrawChart(Canvas container)
        {
            container.Width = leftMargin + DataSource.Count * chartWidth * 2 + rightMargin;
            for (int i = 0; i < DataSource.Count; i++)
            {
                //LinearGradientBrush brush = new LinearGradientBrush();
                
//GradientStopCollection c = new GradientStopCollection();
                
//c.Add(new GradientStop { Color = GetRandomColor(),Offset=0.0});
                
//c.Add(new GradientStop { Color = GetRandomColor(), Offset = 1.0});
                
//brush.GradientStops = c;

                Rectangle rect = new Rectangle
                {
                    Width = chartWidth,
                    Height =  DataSource[i].ChartValue * yStepSize,
                    Fill = new SolidColorBrush(GetRandomColor()),
                    ToolTip = DataSource[i].ChartValue.ToString()
                };

                rect.RadiusX = 5;
                rect.RadiusY = 5;

                Canvas.SetLeft(rect, leftMargin + (leftChartMargin + leftChartMargin / 2) + i * (chartWidth + rightChartMargin / 2));
                Canvas.SetTop(rect, endY - rect.Height);
                MainContainer.Children.Add(rect);
            }
        }
 
   步骤五:画画数据列表,因为数据列表显示的时候如果是水平放置的话会挡住其他的列表值,所以这里需要对数据列表进行转换。具体代码如下:
  
View Code
        /// <summary>
        
/// 画数据列表值
        
/// </summary>
        
/// <param name="container"></param>
        public void DrawXValue(Canvas container)
        {
            container.Width = leftMargin + DataSource.Count * chartWidth * 2 + rightMargin;
            for (int i = 0; i < DataSource.Count; i++)
            {
                TextBlock t = new TextBlock
                {
                    Margin = new Thickness(5),
                    Text = DataSource[i].Name,
                    TextAlignment = TextAlignment.Center,
                    Foreground = TitleForeground
                };
                t.RenderTransform = new RotateTransform { CenterX =5,CenterY=5,Angle = 60 };
                Canvas.SetLeft(t, leftMargin +10 + (leftChartMargin + leftChartMargin / 2) + i * (chartWidth + rightChartMargin / 2));
                Canvas.SetTop(t, endY);
                MainContainer.Children.Add(t);
            }
        }
 
  步骤六:感觉Y轴刻度值跟X轴的数据列表的显示颜色都是一致为黑色,这样不好看,为了方便大家使用,这里定义了两个依赖属性来控制前景颜色。具体代码如下:
  
View Code
        /// <summary>
        
/// 数据标题颜色
        
/// </summary>
        public static readonly DependencyProperty TitleForegroundProperty = DependencyProperty.Register("TitleForeground",typeof(SolidColorBrush),typeof(RectangeChart),new PropertyMetadata(Brushes.Brown));
        public SolidColorBrush TitleForeground
        {
            get { return (SolidColorBrush)GetValue(TitleForegroundProperty); }
            set
            {
                SetValue(TitleForegroundProperty,value);
            }
        }

        /// <summary>
        
/// 刻度列表颜色
        
/// </summary>
        public static readonly DependencyProperty ValueForegroundProperty = DependencyProperty.Register("ValueForeground"typeof(SolidColorBrush), typeof(RectangeChart), new PropertyMetadata(Brushes.Black));
        public SolidColorBrush ValueForeground
        {
            get { return (SolidColorBrush)GetValue(ValueForegroundProperty); }
            set
            {
                SetValue(ValueForegroundProperty, value);
            }
        }
  这样一个简单的RectangleChart控件就完成了。
 
 4.测试:采用MVVM来进行测试。
   
    我们可以用MVVM的架构来进行测试,先定义一个View(RectangleView) 
  
View Code
<UserControl x:Class="SmlAnt.Library.Views.ControlsTest.RectangeChart"
             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" 
             mc:Ignorable="d" 
             xmlns:Controls="clr-namespace:SmlAnt.Library.Controls"
             d:DesignHeight="300" d:DesignWidth="300"             >
    <Grid>
        <ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
            <Controls:RectangeChart DataSource="{Binding DataSource}" TitleForeground="Brown" ValueForeground="Black"/>
        </ScrollViewer>
    </Grid>
</UserControl>
  然后再定义ViewModel(RectangleViewModel)
  
View Code
public class RectangleChartViewModel:ViewModelBase
    {
        Random random = new Random();
        private List<Controls.ChartData> _dataSource;
        public List<Controls.ChartData> DataSource
        {
            get { return _dataSource; }
            set
            {
                if (_dataSource != value)
                {
                    _dataSource = value;
                    RaisePropertyChanged("DataSource");
                }
            }
        }

        public RectangleChartViewModel()
        {
            
            var temp = new List<Controls.ChartData> { };
            for (int i = 1; i <= 10; i++)
            {
                Controls.ChartData data = new Controls.ChartData
                {
                    ChartValue = random.Next(40,100),
                    Name = "数据"+i.ToString()                    
                };
                temp.Add(data);
            }

            DataSource = temp;
        }
    }
   
  
  
  
  
  

 

    5.总结:

 这个自定义RectangleChart的开发只是一个简单的而且不完善的控件开发,只是凭借自己的一种兴趣和思路的分享。希望能够给大家带来一些帮助。同时也欢迎园子里的朋友多提出一些宝贵的意见。共同讨论、共同进步、一起分享。

  

posted @ 2011-09-28 00:59  Smlant.  阅读(2251)  评论(8编辑  收藏  举报