Silverlight 雷达图和一种特殊泡泡画法
自上次发了雷达图,也没怎么说一下。
这次又做了一种图,继续共享一下,就是以一个点为中心,周围绕着几个点,用一个箭头与中心相连并带有某些信息。圆 和中心都可以响应鼠标事件。
我一向不会讲解所做的东西。所以大部分我直接上源码的。
简单讲解一下:
代码结构分为4部分,
1.
首先是画布,其实就是一个canvas因为现在只有二种图,
一个雷达画布 RadarCanvas(比较特殊),和一个二维坐标画布 CoorCanvas,都派生自ChartCanvas;
2.就是轴
坐标轴CoorAxis 和RadarAxis 都派生自IAxis,其实里面什么也没有。就是一个存一些值,比较这个轴的颜色,它的起始与终结坐标。雷达轴的角度偏移等,。
3.model
存值的类了,暂时有 clm泡泡图的点击事件参数 CLMArrowClickEventArg,DataPoint图坐标点,图点点击事件参数ItemClickEventArg,字段映射ItemMapping
4.图形
现有CLMBubbleSeries,它是一个特殊的泡泡图,我们项目中用到的。这里就不说它有什么用。只讲实现。
RadarSeries,它就是雷达图形
它们都继承自ISeries
核心就是讲Series怎么画的
首先看它们的基类:
/// <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)); }
初始化它的颜色。
最重要的是二个函数
/// <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来画周围的小圆。
/// <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); }
啊。我是正的不太会讲解。直接上源码算了
源码地址:源码