Silverlight 雷达图和一种特殊泡泡画法

 

自上次发了雷达图,也没怎么说一下。

这次又做了一种图,继续共享一下,就是以一个点为中心,周围绕着几个点,用一个箭头与中心相连并带有某些信息。圆 和中心都可以响应鼠标事件。

我一向不会讲解所做的东西。所以大部分我直接上源码的。

简单讲解一下:

代码结构分为4部分,

1.

首先是画布,其实就是一个canvas因为现在只有二种图,

一个雷达画布 RadarCanvas(比较特殊),和一个二维坐标画布 CoorCanvas,都派生自ChartCanvas;

2.就是轴

坐标轴CoorAxis 和RadarAxis 都派生自IAxis,其实里面什么也没有。就是一个存一些值,比较这个轴的颜色,它的起始与终结坐标。雷达轴的角度偏移等,。

 

3.model

存值的类了,暂时有 clm泡泡图的点击事件参数 CLMArrowClickEventArg,DataPoint图坐标点,图点点击事件参数ItemClickEventArg,字段映射ItemMapping

 

4.图形

现有CLMBubbleSeries,它是一个特殊的泡泡图,我们项目中用到的。这里就不说它有什么用。只讲实现。

RadarSeries,它就是雷达图形

它们都继承自ISeries

 

核心就是讲Series怎么画的

 

首先看它们的基类:

View Code
/// <summary>
    /// 图表线或图接口
    /// </summary>
    public abstract class ISeries : Common.IBaseControl
    {
        public ISeries(ChartCanvas canvas)
        {
            Canvas = canvas;
            
            Points = new System.Collections.ObjectModel.ObservableCollection<Model.DataPoint>();

            ItemMappings = new System.Collections.ObjectModel.ObservableCollection<Model.ItemMapping>();
        }

        /// <summary>
        /// 动画执行时间
        /// </summary>
        protected const int AnimateDurtion = 1000;

        /// <summary>
        /// 项单击事件
        /// </summary>
        public EventHandler<Model.ItemClickEventArg> ItemClick;

        /// <summary>
        /// 画布
        /// </summary>
        public ChartCanvas Canvas { get; set; }

        /// <summary>
        /// 当前颜色
        /// </summary>
        public Brush Stroke
        {
            get;
            set;
        }

        /// <summary>
        /// 填充色
        /// </summary>
        public Brush Fill
        {
            get;
            set;
        }

        /// <summary>
        /// 图例名
        /// </summary>
        public string LegendLabel
        {
            get;
            set;
        }

        /// <summary>
        /// 当前线的label格式
        /// /// #Y=当前值,#YName=当前Y轴名称,#C{列名}=表示绑定当前数据对象的指定列值
        /// </summary>
        public string ItemTooltipFormat { get; set; }

        /// <summary>
        /// 当前绑定的对象
        /// </summary>
        public object DataContext { get; set; }

        /// <summary>
        /// 当前索引
        /// </summary>
        public int Index { get; set; }

        /// <summary>
        /// 图点
        /// </summary>
        public System.Collections.ObjectModel.ObservableCollection<Model.DataPoint> Points { get; set; }

        /// <summary>
        /// 当前图型属性映射
        /// </summary>
        public System.Collections.ObjectModel.ObservableCollection<Model.ItemMapping> ItemMappings
        {
            get;
            internal set;
        }

        /// <summary>
        /// 获取对象的属性的值
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object GetValue(string name)
        {
            var mapping = GetMapping(name);
            if (mapping != null) name = mapping.MemberName;

            var obj = Common.Helper.GetPropertyName(DataContext, name);
            return obj;
        }

        /// <summary>
        /// 获取对象的值
        /// </summary>
        /// <param name="name">属性名</param>
        /// <returns></returns>
        public double GetNumberValue(string name)
        {
            double value = 0;
            var obj = GetValue(name);
            if (obj != null)
            {
                if (Silverlight.Common.Data.TypeHelper.IsNumber(obj.GetType()))
                {
                    if (!double.TryParse(obj.ToString(), out value))
                    {
                        value = Index + 1;
                    }
                }
                else
                {
                    value = Index + 1;
                }
            }
            return value;
        }

        /// <summary>
        /// 获取指定的字段映射
        /// </summary>
        /// <param name="dm"></param>
        /// <returns></returns>
        public Model.ItemMapping GetMapping(Model.ItemMapping.EnumDataMember dm)
        {
            foreach (var m in ItemMappings)
            {
                if (m.DataMember == dm) return m;
            }
            return null;
        }

        /// <summary>
        /// 获取指定的字段映射
        /// </summary>
        /// <param name="dm"></param>
        /// <returns></returns>
        public Model.ItemMapping GetMapping(string name)
        {
            foreach (var m in ItemMappings)
            {
                if (name.Equals(m.OldName, StringComparison.OrdinalIgnoreCase) ||
                    name.Equals(m.MemberName, StringComparison.OrdinalIgnoreCase) ||
                    name.Equals(m.DisplayName, StringComparison.OrdinalIgnoreCase)) 
                    return m;
            }
            return null;
        }

        /// <summary>
        /// 获取指定的字段映射
        /// </summary>
        /// <param name="dm"></param>
        /// <returns></returns>
        public System.Collections.Generic.IEnumerable<Model.ItemMapping> GetMappings(Model.ItemMapping.EnumDataMember dm)
        {
            var ms = (from m in ItemMappings
                      where m.DataMember == dm
                      select m).ToArray<Model.ItemMapping>();
            return ms;
        }

        /// <summary>
        /// 当前动画
        /// </summary>
        protected Storyboard storyboard;

        /// <summary>
        /// 展现
        /// </summary>
        public virtual void Draw()
        {
            var ps = CreatePath();
            foreach (var p in ps)
            {
                Canvas.AddChild(p);
            }
            if (storyboard != null && Canvas.IsAnimate)
            {
                storyboard.Begin();
            }
        }

        System.Collections.Generic.List<Shape> shaps=new System.Collections.Generic.List<Shape>();
        /// <summary>
        /// 当前线条
        /// </summary>
        public System.Collections.Generic.List<Shape> Shaps
        {
            get { return shaps; }
            protected set { shaps = value; }
        }

        /// <summary>
        /// 生成图形
        /// </summary>
        /// <returns></returns>
        public virtual System.Collections.Generic.IEnumerable<Shape> CreatePath()
        {
            return Shaps;
        }

        /// <summary>
        /// 生成图例
        /// </summary>
        /// <returns></returns>
        internal virtual StackPanel CreateLegend()
        {
            if (!string.IsNullOrWhiteSpace(LegendLabel))
            {
                var panel = new StackPanel();
                panel.Orientation = Orientation.Horizontal;
                var colorarea = new Rectangle();
                colorarea.Width = 20;
                colorarea.Height = 10;
                colorarea.Fill = this.Fill;
                colorarea.Stroke = this.Stroke;
                panel.Margin = new Thickness(2);
                panel.Children.Add(colorarea);

                var text = new TextBlock();
                text.Margin = new Thickness(2);

                var dic=new System.Collections.Generic.Dictionary<string,string>();
                foreach (var m in ItemMappings)
                {
                    if (!dic.ContainsKey("YName") && !string.IsNullOrWhiteSpace(m.DisplayName))
                    {
                        dic.Add("YName", m.DisplayName??m.MemberName);
                    }
                }

                text.Text = Common.Helper.DserLabelName(LegendLabel,dic ,
                        (string name) =>
                        {
                            return GetValue(name);
                        });
                text.Foreground = new SolidColorBrush(Colors.Black);
                panel.Children.Add(text);

                return panel;
            }
            return null;
        }

        /// <summary>
        /// 添加点的小圆圈,方便鼠标点中。并加提示
        /// </summary>
        /// <param name="center"></param>
        /// <param name="rotate"></param>
        protected Ellipse AddPoint(Point center, double rotate,object tooltip,Model.DataPoint p)
        {
            var circle = Common.Helper.CreateEllipse(center, rotate);
            circle.Stroke = this.Stroke;
            circle.Fill = this.Fill;
            ToolTipService.SetToolTip(circle, tooltip);

            if (this.ItemClick != null) {
                circle.Cursor = Cursors.Hand;
                circle.MouseLeftButtonUp += (sender, e) => {
                    var arg = new Model.ItemClickEventArg() { 
                     Data=this.DataContext,
                      Item=p
                    };
                    ItemClick(circle,arg);
                };
            }

            Canvas.AddChild(circle);

            System.Windows.Controls.Canvas.SetZIndex(circle, Common.BaseParams.TooltipZIndex);

            return circle;
        }

        /// <summary>
        /// 生成提示信息
        /// #Y=当前值,#YName=当前Y轴名称,#C{列名}=表示绑定当前数据对象的指定列值
        /// </summary>
        /// <returns></returns>
        protected string CreateTooltip(string yName)
        {
            if (!string.IsNullOrWhiteSpace(this.ItemTooltipFormat))
            {
                var yvalue = GetValue(yName);
                var tmp = Common.Helper.DserLabelName(this.ItemTooltipFormat,
                    new System.Collections.Generic.Dictionary<string, string>() { { "YName", yName }, { "Y", yvalue==null?"":yvalue.ToString() } }, 
                    (string name) =>
                    {
                        return GetValue(name);
                    });
                return tmp;
            }
            return this.ItemTooltipFormat;
        }

        public void Show()
        {
            throw new NotImplementedException();
        }

        public void Hide()
        {
            throw new NotImplementedException();
        }
    }

嗯。没有很多东西,都是一些基础操作,和几个接口。

 

下面就可以看泡泡图CLMBubbleSeries:

其构造函数:

 public CLMBubbleSeries(CoorCanvas canvas)
            : base(canvas) {
                this.Stroke = new SolidColorBrush(Color.FromArgb(255, 51, 153, 255));
                this.Fill = new SolidColorBrush(Color.FromArgb(255, 188, 222, 255));
        }

初始化它的颜色。

最重要的是二个函数

View Code
/// <summary>
        /// 生成当前图形
        /// </summary>
        /// <returns></returns>
        public override System.Collections.Generic.IEnumerable<Shape> CreatePath()
        {
            if (storyboard != null) storyboard.Stop();
            if (Canvas.IsAnimate) this.storyboard = new Storyboard();

            this.Shaps.Clear();
            if (DataContext == null) return base.CreatePath();
            var data = DataContext as System.Collections.ICollection;

            var circlesize = data.Count > 20 ? circleSize / data.Count * 20 : circleSize;
            var center=new Point() { X = this.Canvas.Width / 2, Y = centerSize * 2.3 };
            var left = Canvas.Margin.Left + circlesize * 2;
            if (left <= circlesize / 2) left = circlesize + 2;
            var bottom = (center.Y + circlesize + centerSize);
            var maxbottom = Canvas.Height - Canvas.Margin.Bottom - circlesize - 4;
            //距离中心距离
            var radiacenter = Math.Min(center.X - left, maxbottom);
            var circleIndex = -1;            

            //小圆个数
            var circlecount = data.Count;
            var rotatestep = 3.78 / circlecount;//每个小圆的角度
            var mapping = GetMapping(Model.ItemMapping.EnumDataMember.Y);

            if (mapping == null) throw new Exception("至少需要指定一个Y轴字段映射");
            //与中心点关联设置
            var links = GetMappings(Model.ItemMapping.EnumDataMember.CLMLink);

            var tocentername="";
            //画泡泡
            foreach (var m in data)
            {
                if (m != null)
                {
                    var item = new Model.DataPoint();
                    
                    item.PotinShape= new Path();
                    var v = Common.Helper.GetPropertyName(m, mapping.MemberName); ;
                    item.PointType = Model.DataPoint.EnumPointType.ChartPoint;
                    item.StringValue = v==null?"":v.ToString();
                    System.Windows.Controls.Canvas.SetZIndex(item.PotinShape, Common.BaseParams.ShapZIndex);
                   
                    var el = new EllipseGeometry();
                    item.PotinShape.Data = el;

                    //画中心位置
                    if (circleIndex == -1)
                    {
                        item.Position = el.Center = center;
                        el.RadiusX = el.RadiusY = centerSize;
                        item.Width = item.Height = centerSize * 2;
                        tocentername = item.StringValue;
                        item.StringValue =(CenterName??mapping.MemberName) + "\n" + item.StringValue;

                        var label = item.CreateLabel();
                        //加入标签
                        Canvas.AddChild(label);

                        if (ItemClick != null)
                        {
                            label.Cursor = Cursors.Hand;
                            var centerdata = m;
                            label.MouseLeftButtonUp += (sender, e) =>
                            {
                                var arg = new Model.ItemClickEventArg()
                                {
                                    Data = centerdata,
                                    Item = item
                                };
                                ItemClick(sender, arg);
                            };
                        }

                        var tootip = CreateTooltip(m);
                        ToolTipService.SetToolTip(label,tootip);
                    }
                    //画边上的小圆
                    else
                    {
                        //初始化小圆点
                        InitPoint(el, item, rotatestep, circleIndex, radiacenter, center, maxbottom, circlesize, tocentername,circlecount,links,m);
                    }
                    
                    if(Canvas.IsFillShape)item.PotinShape.Fill = this.Fill;
                    item.PotinShape.Stroke = this.Stroke;
                    item.PotinShape.StrokeThickness = Canvas.LineWidth;
                    this.Shaps.Add(item.PotinShape);
                    
                    circleIndex++;                    
                }
            }

            return base.CreatePath();
        }

画一个中心圆 ,和用InitPoint来画周围的小圆。

View Code
/// <summary>
        /// 初始化项
        /// </summary>
        /// <param name="el"></param>
        /// <param name="item"></param>
        /// <param name="rotatestep"></param>
        /// <param name="circleIndex"></param>
        /// <param name="radiacenter"></param>
        /// <param name="center"></param>
        /// <param name="maxbottom"></param>
        /// <param name="circlesize"></param>
        /// <param name="tocentername"></param>
        private void InitPoint(EllipseGeometry el,Model.DataPoint item,
            double rotatestep, int circleIndex, double radiacenter, Point center,
            double maxbottom, double circlesize, string tocentername, int circlecount,
            System.Collections.Generic.IEnumerable<Model.ItemMapping> links,object data)
        {
            var position = new Point();            
            var rotate = rotatestep * circleIndex + 2.95;
            var rsin = Math.Sin(rotate);
            var rcos = Math.Cos(rotate);
            //二圆偏移量           
            var ystep = rsin * radiacenter;
            var xstep = rcos * radiacenter;

            position.X = center.X + xstep;
            position.Y = center.Y - ystep;

            if (position.Y >= maxbottom) position.Y = maxbottom;

            item.Position = position;
            el.RadiusX = el.RadiusY = circlesize;
            item.Width = item.Height = circlesize * 2;

            var arrow = new Controls.CLMArrow(Canvas);
            arrow.Fill = this.Fill;
            arrow.Stroke = this.Stroke;
            arrow.Rotate = rotate;
            arrow.ToName = tocentername;
            arrow.FromName = item.StringValue;
            arrow.RotateSin = rsin;
            arrow.RotateCos = rcos;

            var startystep = (circlesize) * arrow.RotateSin;
            var startxstep = (circlesize) * arrow.RotateCos;
            arrow.StartPoint = new Point(item.Position.X - startxstep, item.Position.Y + startystep);
            var endystep = centerSize * arrow.RotateSin;
            var endxstep = centerSize * arrow.RotateCos;
            arrow.EndPoint = new Point(center.X + endxstep, center.Y - endystep);

            if (links != null)
            {
                var count = links.Count<Model.ItemMapping>();
                if (count > 0)
                {
                    var lnk = links.ElementAt<Model.ItemMapping>(0);
                    var tmp = Common.Helper.GetPropertyName(data, lnk.MemberName);
                    if (!string.IsNullOrWhiteSpace(lnk.MarkName)) arrow.FromMarkName = lnk.MarkName;
                    arrow.FromValue = tmp == null ? "" : tmp.ToString();
                }
                if (count > 1)
                {
                    var lnk = links.ElementAt<Model.ItemMapping>(1);
                    var tmp = Common.Helper.GetPropertyName(data, lnk.MemberName);
                    if (!string.IsNullOrWhiteSpace(lnk.MarkName)) arrow.ToMarkName = lnk.MarkName;
                    arrow.ToValue = tmp == null ? "" : tmp.ToString();
                }
            }

            //设置箭头提示事件
            if (ArrowTooltipClick != null) arrow.SetClickEvent(ArrowTooltipClick);

            arrow.Draw();
            item.TargetControl = arrow;

            var label = item.CreateLabel();
            Canvas.AddChild(label);

            if (ItemClick != null)
            {
                label.Cursor = Cursors.Hand;
                label.MouseLeftButtonUp += (sender, e) =>
                {
                    var arg = new Model.ItemClickEventArg()
                    {
                        Data = data,
                        Item = item
                    };
                    ItemClick(sender, arg);
                };
            }

            if (Canvas.IsAnimate)
            {
                label.Visibility = Visibility.Collapsed;
                var anima = new PointAnimation();
                anima.To = position;
                anima.Duration = TimeSpan.FromMilliseconds(AnimateDurtion);

                Storyboard.SetTarget(anima, el);
                el.Center = center;
                Storyboard.SetTargetProperty(anima, new PropertyPath("Center"));

                var sizeanimax = new DoubleAnimation();
                sizeanimax.From = 0;
                sizeanimax.To = circlesize;
                Storyboard.SetTarget(sizeanimax, el);
                Storyboard.SetTargetProperty(sizeanimax, new PropertyPath("RadiusX"));

                var sizeanimay = new DoubleAnimation();
                sizeanimay.From = 0;
                sizeanimay.To = circlesize;
                Storyboard.SetTarget(sizeanimay, el);
                Storyboard.SetTargetProperty(sizeanimay, new PropertyPath("RadiusY"));

                anima.Completed += new EventHandler((sender, e) =>
                {
                    label.Visibility = Visibility.Visible;
                    InitMouseEvent(label, arrow);
                    if (circleIndex == circlecount / 2 - 1) { 
                        arrow.Show();
                        currentShowedArrow = arrow;
                    }
                });
                this.storyboard.Children.Add(anima);
                this.storyboard.Children.Add(sizeanimax);
                this.storyboard.Children.Add(sizeanimay);
            }
            else
            {
                el.Center = position;
                //加入标签
                //var label = item.CreateLabel();
                //Canvas.AddChild(label);
                InitMouseEvent(label, arrow);
                if (circleIndex == circlecount / 2 - 1) { 
                    arrow.Show();
                    currentShowedArrow = arrow;
                }
            }
        }

 

最后是画坐标图代码:

       /// <summary>
        /// 画坐标图
        /// </summary>
        private void DrawCoor()
        {
            if (!IsDrawBaseLine) return;

            coorGeometry.Figures.Clear();

            var xaxis = new Axis.CoorAxis();
            xaxis.AxisShap = coorPath;
            xaxis.AType = Axis.AxisType.XValue;

            var yaxis = new Axis.CoorAxis();
            yaxis.AType = Axis.AxisType.YValue;
            yaxis.AxisShap = coorPath;
            
            this.Axises.Add(xaxis);
            this.Axises.Add(yaxis);

            var coorfigure = new PathFigure();
            coorGeometry.Figures.Add(coorfigure);
            
            //画上箭头
            yaxis.StartPoint = coorfigure.StartPoint = new Point(Margin.Left, Margin.Top - arrowMargin);
            var tlp = new Point() { X = Margin.Left - arrowMargin, Y = Margin.Top + arrowMargin };
            coorfigure.Segments.Add(new LineSegment() { Point = tlp });
            coorfigure.Segments.Add(new LineSegment() { Point = tlp });
            coorfigure.Segments.Add(new LineSegment() { Point = coorfigure.StartPoint });
            var trp = new Point() { X = Margin.Left + arrowMargin, Y = Margin.Top + arrowMargin };
            coorfigure.Segments.Add(new LineSegment() { Point = trp });
            coorfigure.Segments.Add(new LineSegment() { Point = trp });
            coorfigure.Segments.Add(new LineSegment() { Point = coorfigure.StartPoint });

            //左侧Y轴
            yaxis.EndPoint = xaxis.StartPoint = new Point() { X = Margin.Left, Y = this.Height - Margin.Bottom };
            coorfigure.Segments.Add(new LineSegment() { Point = xaxis.StartPoint });
            //x轴
            xaxis.EndPoint = new Point() { X = this.Width - Margin.Right + arrowMargin, Y = xaxis.StartPoint.Y };
            coorfigure.Segments.Add(new LineSegment() { Point = xaxis.EndPoint });
            
            //画右箭头
            var brtp = new Point() { X = this.Width - Margin.Right - arrowMargin, Y = xaxis.EndPoint.Y - arrowMargin };
            var brbp = new Point() { X = brtp.X, Y = xaxis.EndPoint.Y + arrowMargin };
            coorfigure.Segments.Add(new LineSegment() { Point = brtp });
            coorfigure.Segments.Add(new LineSegment() { Point = brtp });
            coorfigure.Segments.Add(new LineSegment() { Point = xaxis.EndPoint });
            coorfigure.Segments.Add(new LineSegment() { Point = brbp });
            coorfigure.Segments.Add(new LineSegment() { Point = brbp });

            AddChild(coorPath);

            DrawLine();//画虚线
        }

        /// <summary>
        /// 画虚线
        /// </summary>
        private void DrawLine()
        {
            var w = this.Width - Margin.Left - Margin.Right;
            var h = this.Height - Margin.Top - Margin.Bottom;

            var vstep = h / HorizontalCount;
         
            for (var i = 1; i <= HorizontalCount; i++)
            {
                var l = new Line();
                l.StrokeLineJoin = PenLineJoin.Round;
                l.StrokeDashArray.Add(4);
                l.Stroke = DashColor;
                l.StrokeThickness = 1;
                l.X1 = Margin.Left;
                l.Y1 = this.Height - Margin.Bottom - (vstep * i);
                l.X2 = this.Width - Margin.Right;
                l.Y2 = l.Y1;
                AddChild(l);
            }

            var xstep = w / VerticalCount;
            for (var i = 1; i <= VerticalCount; i++)
            {
                var l = new Line();
                l.Stroke = DashColor;
                l.StrokeDashArray.Add(4);
                l.StrokeThickness = 1;
                l.X1 = Margin.Left + xstep * i;
                l.Y1 = Margin.Top;
                l.X2 = l.X1;
                l.Y2 = this.Height - Margin.Bottom;
                AddChild(l);
            }

 

 啊。我是正的不太会讲解。直接上源码算了

源码地址:源码

posted on 2012-06-21 17:16  家猫47  阅读(2487)  评论(0编辑  收藏  举报

导航