欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

WPF 自定义雷达图开发实例教程

程序员文章站 2022-03-26 08:42:50
自定义雷达图表如下: 1、创建usercontrol,名为“radarchartcontrol” 前台:

自定义雷达图表如下:

WPF 自定义雷达图开发实例教程

1、创建usercontrol,名为“radarchartcontrol”

前台:

<usercontrol x:class="wpfapplication2.radarchartcontrol" 
 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" 
 d:designheight="300" d:designwidth="300" loaded="radarchartcontrol_onloaded">
 <canvas x:name="canvaspanel" horizontalalignment="center" verticalalignment="center">
 </canvas>
</usercontrol>

后台:

/// <summary>
 /// radarchartcontrol.xaml 的交互逻辑
 /// </summary>
 public partial class radarchartcontrol : usercontrol
 {
 public radarchartcontrol()
 {
  initializecomponent();
 }
 #region 属性
 /// <summary>
 /// 尺寸大小
 /// 高宽大小一样
 /// </summary>
 public double size
 {
  get { return (double)getvalue(sizeproperty); }
  set { setvalue(sizeproperty, value); }
 }
 public static readonly dependencyproperty sizeproperty = dependencyproperty.register("size", typeof(double),
 typeof(radarchartcontrol), new propertymetadata(400.0));
 /// <summary>
 /// 标题
 /// </summary>
 public list<argumentmodel> arguments
 {
  get { return (list<argumentmodel>)getvalue(argumentsproperty); }
  set { setvalue(argumentsproperty, value); }
 }
 public static readonly dependencyproperty argumentsproperty = dependencyproperty.register("arguments", typeof(list<argumentmodel>),
 typeof(radarchartcontrol), new propertymetadata(new list<argumentmodel>()));
 /// <summary>
 /// 数据
 /// </summary>
 public list<chartitem> datas
 {
  get { return (list<chartitem>)getvalue(datasproperty); }
  set { setvalue(datasproperty, value); }
 }
 public static readonly dependencyproperty datasproperty = dependencyproperty.register("datas", typeof(list<chartitem>),
 typeof(radarchartcontrol), new propertymetadata(new list<chartitem>()));
 /// <summary>
 /// 获取或设置线条颜色
 /// </summary>
 public brush borderbrush
 {
  get { return (brush)getvalue(borderbrushproperty); }
  set { setvalue(borderbrushproperty, value); }
 }
 public static readonly dependencyproperty borderbrushproperty = dependencyproperty.register("borderbrush", typeof(brush),
 typeof(radarchartcontrol), new propertymetadata(brushes.royalblue));
 /// <summary>
 /// 连接点大小
 /// </summary>
 public int ellipsesize = 7;
 /// <summary>
 /// 控件大小
 /// </summary>
 public double totalsize
 {
  get
  {
  double size = size + 200;
  return size;
  }
 }
 /// <summary>
 /// 面板
 /// </summary>
 public canvas chartcanvas = new canvas();
 //声明和注册路由事件
 public static readonly routedevent titleclickroutedevent =
 eventmanager.registerroutedevent("titleclick", routingstrategy.bubble, typeof(eventhandler<routedeventargs>), typeof(radarchartcontrol));
 //clr事件包装
 public event routedeventhandler titleclick
 {
  add { this.addhandler(titleclickroutedevent, value); }
  remove { this.removehandler(titleclickroutedevent, value); }
 }
 //激发路由事件,借用click事件的激发方法
 protected void onclick(object sender, routedeventargs e)
 {
  routedeventargs args = new routedeventargs(titleclickroutedevent, e);
  this.raiseevent(args);
 }
 #endregion
 private void radarchartcontrol_onloaded(object sender, routedeventargs e)
 {
  if (!checkdata())
  {
  throw new exception("radarchart的数据之间不匹配!请重新配置!");
  }
  //获取最大数值
  int maxdata = datas.max(i => i.datalist.max(o => o.data));
  //设置面板和背景
  setcanvasandbackground(maxdata);
  //设置数据标题
  setdatatitle(datas);
  //获取半圈大小
  double length = size / 2 / maxdata;
  //连接点半径
  int ellipser = ellipsesize / 2;
  foreach (var chartitem in datas)
  {
  var color = chartitem.color;
  //俩个多边形,一个设置背景,一个设置边框
  polygon polygonarea = new polygon() { fill = color, opacity = 0.2, strokethickness = 0 };
  polygon polygonborder = new polygon() { fill = brushes.transparent, stroke = color, strokethickness = 0.8 };
  int index = 0;
  foreach (var data in chartitem.datalist)
  {
   double currentangle = angle * index + 90;
   double angle = (currentangle / 360) * 2 * math.pi;
   var r = data.data * length;
   double x = size / 2 + r * math.cos(angle);
   double y = size / 2 - r * math.sin(angle);
   //多边形添加节点
   var point = new point()
   {
   x = x,
   y = y
   };
   polygonarea.points.add(point);
   polygonborder.points.add(point);
   //设置节点style
   var ellipse = new ellipse() { width = ellipsesize, height = ellipsesize, fill = color };
   canvas.setleft(ellipse, x - ellipser);
   canvas.settop(ellipse, y - ellipser);
   chartcanvas.children.add(ellipse);
   index++;
  }
  chartcanvas.children.add(polygonarea);
  chartcanvas.children.add(polygonborder);
  }
  //设置标题
  setarguments();
 }
 /// <summary>
 /// 设置数据标题
 /// </summary>
 /// <param name="datas"></param>
 private void setdatatitle(list<chartitem> datas)
 {
  radarcharttitlelist titlelist = new radarcharttitlelist();
  titlelist.itemsoure = datas;
  double angle = math.pi * 0.25;
  double x = totalsize / 2 + (totalsize / 2) * math.sin(angle);
  canvas.setleft(titlelist, x);
  canvas.settop(titlelist, x);
  canvaspanel.children.add(titlelist);
 }
 /// <summary>
 /// 设置标题
 /// </summary>
 private void setarguments()
 {
  int index = 0;
  foreach (var argument in arguments)
  {
  var button = new chartbutton();
  button.content = argument.name;
  button.icon = argument.iconsource;
  button.mybutton.click += onclick;
  //绘制xy
  double currentangle = angle * index + 90;
  double angle = (currentangle / 360) * 2 * math.pi;
  var r = totalsize / 2;
  double x = r + r * math.cos(angle) - (button.width / 2);
  double y = r - r * math.sin(angle) - (button.height / 2);
  //添加按钮高度差异
  y = y + math.sin(angle) * (button.width / 2 - button.height / 2);
  canvas.setleft(button, x);
  canvas.settop(button, y);
  canvaspanel.children.add(button);
  index++;
  }
 }
 /// <summary>
 /// 检查数据
 /// </summary>
 /// <returns></returns>
 private bool checkdata()
 {
  if (datas == null)
  {
  return false;
  }
  foreach (var data in datas)
  {
  bool result = !datas.any(i => i.datalist.count != data.datalist.count);
  if (!result)
  {
   return false;
  }
  }
  return true;
 }
 /// <summary>
 /// 设置面板和背景
 /// </summary>
 /// <param name="maxindex"></param>
 private void setcanvasandbackground(int maxindex)
 {
  canvaspanel.height = totalsize;
  canvaspanel.width = totalsize;
  //面板
  chartcanvas.height = size;
  chartcanvas.width = size;
  double canvasx = (totalsize - size) / 2;
  canvas.setleft(chartcanvas, canvasx);
  canvas.settop(chartcanvas, canvasx);
  canvaspanel.children.add(chartcanvas);
  //画圈和直线
  var color = borderbrush;
  double length = size / 2 / maxindex;
  for (int i = 0; i < maxindex; i++)
  {
  double height = length * 2 * (i + 1);
  double left = size / 2 - length * (i + 1);
  var ellipse = new ellipse() { stroke = color, strokethickness = 0.5, height = height, width = height };
  canvas.setleft(ellipse, left);
  canvas.settop(ellipse, left);
  chartcanvas.children.add(ellipse);
  }
  //暂时设定:4个标题时,画线
  if (arguments.count == 4)
  {
  //竖向直线
  path verticalpath = new path()
  {
   stroke = color,
   strokethickness = 0.2,
  };
  //添加数据
  streamgeometry geometry = new streamgeometry();
  geometry.fillrule = fillrule.nonzero; //声前f0还是f1,现在是f1
  using (streamgeometrycontext ctx = geometry.open())
  {
   ctx.beginfigure(new point(size / 2, 0), true, true);
   ctx.lineto(new point(size / 2, size), true, false);
  }
  geometry.freeze();
  verticalpath.data = geometry;
  chartcanvas.children.add(verticalpath);
  //横向直线
  path horizontalpath = new path()
  {
   stroke = color,
   strokethickness = 0.2,
  };
  //添加数据
  geometry = new streamgeometry();
  geometry.fillrule = fillrule.nonzero; //声前f0还是f1,现在是f1
  using (streamgeometrycontext ctx = geometry.open())
  {
   ctx.beginfigure(new point(0, size / 2), true, true);
   ctx.lineto(new point(size, size / 2), true, false);
  }
  geometry.freeze();
  horizontalpath.data = geometry;
  chartcanvas.children.add(horizontalpath);
  }
 }
 /// <summary>
 /// 分隔角度
 /// </summary>
 private double angle
 {
  get
  {
  int count = arguments.count;
  double angle = 360 / count;
  return angle;
  }
 }
 }
 /// <summary>
 /// 类标题
 /// </summary>
 public class argumentmodel
 {
 public imagesource iconsource { get; set; }
 public string name { get; set; }
 }
 /// <summary>
 /// 单组数据
 /// </summary>
 public class chartitem
 {
 public brush color { get; set; }
 list<chartdata> datalist = new list<chartdata>();
 public list<chartdata> datalist
 {
  get { return datalist; }
  set { datalist = value; }
 }
 public object name { get; set; }
 }
 /// <summary>
 /// 数据
 /// </summary>
 public class chartdata
 {
 public string name { get; set; }
 public int data { get; set; }
 }

2、创建标题类按钮控件,定义名称为chartbutton

WPF 自定义雷达图开发实例教程

WPF 自定义雷达图开发实例教程

前台:

<usercontrol x:class="wpfapplication2.chartbutton"
  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"
  d:designheight="80" d:designwidth="200" loaded="chartbutton_onloaded">
 <usercontrol.resources>
 <style targettype="button">
  <setter property="foreground" value="white"></setter>
 </style>
 </usercontrol.resources>
 <grid>
 <button x:name="mybutton" verticalalignment="center" horizontalalignment="center">
  <button.template>
  <controltemplate targettype="{x:type button}">
   <grid x:name="buttongrid" height="{templatebinding height}">
   <rectangle x:name="buttonretc" radiusx="20" radiusy="25" stroke="#ff06ffe8"></rectangle>
   <stackpanel orientation="horizontal" margin="20,5" horizontalalignment="center">
    <rectangle height="{binding iconheight}" width="{binding iconwidth}">
    <rectangle.fill>
     <imagebrush imagesource="{binding icon}"></imagebrush>
    </rectangle.fill>
    </rectangle>
    <textblock x:name="buttontextblock" text="{templatebinding content}" foreground="{templatebinding foreground}" margin="8,-2,0,0" fontsize="22" verticalalignment="center" textalignment="center"></textblock>
   </stackpanel>
   </grid>
   <controltemplate.triggers>
   <datatrigger binding="{binding elementname=mybutton,path=isfocused}" value="true">
    <datatrigger.setters>
    <setter targetname="buttonretc" property="fill" value="#ffa9bcff"></setter>
    <setter targetname="buttonretc" property="strokethickness" value="0.5"></setter>
    <setter targetname="buttontextblock" property="foreground" value="#ff06ffe8"></setter>
    </datatrigger.setters>
   </datatrigger>
   <datatrigger binding="{binding elementname=mybutton,path=ispressed}" value="true">
    <datatrigger.setters>
    <setter targetname="buttontextblock" property="fontweight" value="bold"></setter>
    </datatrigger.setters>
   </datatrigger>
   <datatrigger binding="{binding elementname=mybutton,path=isfocused}" value="false">
    <datatrigger.setters>
    <setter targetname="buttonretc" property="fill" value="transparent"></setter>
    <setter targetname="buttonretc" property="strokethickness" value="0"></setter>
    </datatrigger.setters>
   </datatrigger>
   </controltemplate.triggers>
  </controltemplate>
  </button.template>
 </button>
 </grid>
</usercontrol>

后台:

/// <summary>
 /// chartbutton.xaml 的交互逻辑
 /// </summary>
 public partial class chartbutton : usercontrol
 {
 public chartbutton()
 {
  initializecomponent();
 }
 #region 属性
 /// <summary>
 /// 工具提示
 /// </summary>
 public string tooltip
 {
  get { return (string)getvalue(tooltipproperty); }
  set { setvalue(tooltipproperty, value); }
 }
 public static readonly dependencyproperty tooltipproperty = dependencyproperty.register("tooltip",
 typeof(string), typeof(chartbutton), new propertymetadata());
 /// <summary>
 /// 按钮内容
 /// </summary>
 public string content
 {
  get { return (string)getvalue(contentproperty); }
  set { setvalue(contentproperty, value); }
 }
 public static readonly dependencyproperty contentproperty = dependencyproperty.register("content",
 typeof(string), typeof(chartbutton), new propertymetadata("按钮"));
 /// <summary>
 /// 图标
 /// </summary>
 public imagesource icon
 {
  get { return (imagesource)getvalue(iconproperty); }
  set { setvalue(iconproperty, value); }
 }
 public static readonly dependencyproperty iconproperty = dependencyproperty.register("icon",
 typeof(imagesource), typeof(chartbutton), new propertymetadata());
 /// <summary>
 /// 图标高度
 /// </summary>
 public double iconheight
 {
  get { return (double)getvalue(iconheightproperty); }
  set { setvalue(iconheightproperty, value); }
 }
 public static readonly dependencyproperty iconheightproperty = dependencyproperty.register("iconheight",
 typeof(double), typeof(chartbutton), new propertymetadata(25.0));
 /// <summary>
 /// 图标宽度
 /// </summary>
 public double iconwidth
 {
  get { return (double)getvalue(iconwidthproperty); }
  set { setvalue(iconwidthproperty, value); }
 }
 public static readonly dependencyproperty iconwidthproperty = dependencyproperty.register("iconwidth",
 typeof(double), typeof(chartbutton), new propertymetadata(25.0));
 /// <summary>
 /// 高度
 /// </summary>
 public double height
 {
  get { return (double)getvalue(heightproperty); }
  set { setvalue(heightproperty, value); }
 }
 public static readonly dependencyproperty heightproperty = dependencyproperty.register("height",
 typeof(double), typeof(chartbutton), new propertymetadata(46.0));
 /// <summary>
 /// 宽度
 /// </summary>
 public double width
 {
  get { return (double)getvalue(widthproperty); }
  set { setvalue(widthproperty, value); }
 }
 public static readonly dependencyproperty widthproperty = dependencyproperty.register("width",
 typeof(double), typeof(chartbutton), new propertymetadata(170.0));
 #endregion
 private void chartbutton_onloaded(object sender, routedeventargs e)
 {
  mybutton.tooltip = tooltip;
  mybutton.content = content;
  mybutton.width = width;
  mybutton.height = height;
  if (icon != null)
  {
  mybutton.datacontext = new chartbuttonmodel()
  {
   icon = icon,
   iconheight = iconheight,
   iconwidth = iconwidth
  };
  }
 }
 }
 public class chartbuttonmodel
 {
 public imagesource icon { get; set; }
 public double iconheight { get; set; }
 public double iconwidth { get; set; }
 }

3、定义数据组标题显示列表

WPF 自定义雷达图开发实例教程

前台:

<usercontrol x:class="wpfapplication2.radarcharttitlelist"
  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" 
  d:designheight="300" d:designwidth="300" loaded="radarcharttitlelist_onloaded">
 <usercontrol.resources>
 <style x:key="itemcontainer" targettype="{x:type listboxitem}">
  <setter property="template">
  <setter.value>
   <controltemplate targettype="{x:type listboxitem}">
   <border x:name="iconborder" background="transparent" cornerradius="4" borderthickness="0">
    <contentpresenter />
   </border>
   <controltemplate.triggers>
    <trigger property="isselected" value="true">
    <setter targetname="iconborder" property="bitmapeffect">
     <setter.value>
     <outerglowbitmapeffect glowcolor="transparent" glowsize="5" />
     </setter.value>
    </setter>
    </trigger>
   </controltemplate.triggers>
   </controltemplate>
  </setter.value>
  </setter>
 </style>
 </usercontrol.resources>
 <grid>
 <listbox x:name="mylistbox" itemssource="{binding}" itemcontainerstyle="{staticresource itemcontainer}" focusvisualstyle="{x:null}">
  <listbox.template>
  <controltemplate>
   <stackpanel background="transparent" isitemshost="true"></stackpanel>
  </controltemplate>
  </listbox.template>
  <listbox.itemtemplate>
  <datatemplate>
   <grid horizontalalignment="left" verticalalignment="center" background="transparent">
   <grid.columndefinitions>
    <columndefinition width="auto"></columndefinition>
    <columndefinition width="*"></columndefinition>
   </grid.columndefinitions>
   <grid horizontalalignment="center" margin="10,0" background="transparent">
    <ellipse fill="{binding color}" height="6" width="6" horizontalalignment="right" verticalalignment="center"></ellipse>
    <canvas verticalalignment="center" horizontalalignment="center">
    <path fill="{binding color}" height="5" strokethickness="1" stroke="{binding color}" verticalalignment="center" data="m-10,0 l10,0"></path>
    </canvas>
   </grid>
   <textblock grid.column="1" text="{binding name}" foreground="white" background="transparent"></textblock>
   </grid>
  </datatemplate>
  </listbox.itemtemplate>
 </listbox>
 </grid>
</usercontrol>

后台:

/// <summary>
 /// radarcharttitlelist.xaml 的交互逻辑
 /// </summary>
 public partial class radarcharttitlelist : usercontrol
 {
 public radarcharttitlelist()
 {
  initializecomponent();
 }
 /// <summary>
 /// 数据
 /// </summary>
 public list<chartitem> itemsoure
 {
  get { return (list<chartitem>)getvalue(itemsoureproperty); }
  set { setvalue(itemsoureproperty, value); }
 }
 public static readonly dependencyproperty itemsoureproperty = dependencyproperty.register("itemsoure", typeof(list<chartitem>),
 typeof(radarchartcontrol), new propertymetadata(new list<chartitem>()));
 private void radarcharttitlelist_onloaded(object sender, routedeventargs e)
 {
  this.datacontext = itemsoure;
 }
 }

4、界面引用控件

<window x:class="wpfapplication2.mainwindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:wpfapplication2="clr-namespace:wpfapplication2"
  title="mainwindow" height="350" width="525" background="lightgray">
 <grid>
  <wpfapplication2:radarchartcontrol x:name="radarchartcontrol" horizontalalignment="center" verticalalignment="center">
   <wpfapplication2:radarchartcontrol.arguments>
    <wpfapplication2:argumentmodel name="c#" iconsource="chart_bar_big.png"></wpfapplication2:argumentmodel>
    <wpfapplication2:argumentmodel name="java" iconsource="blueprint_blog.png"></wpfapplication2:argumentmodel>
    <wpfapplication2:argumentmodel name="python" iconsource="chart_graph_descending.png"></wpfapplication2:argumentmodel>
    <wpfapplication2:argumentmodel name="vb" iconsource="chart_bar_big.png"></wpfapplication2:argumentmodel>
    <wpfapplication2:argumentmodel name="其它" iconsource="chart_graph_descending.png"></wpfapplication2:argumentmodel>
   </wpfapplication2:radarchartcontrol.arguments>
   <wpfapplication2:radarchartcontrol.datas>
    <wpfapplication2:chartitem name="应聘者a" color="#ff07c507">
     <wpfapplication2:chartitem.datalist>
      <wpfapplication2:chartdata data="1"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="3"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="3"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="4"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="4"></wpfapplication2:chartdata>
     </wpfapplication2:chartitem.datalist>
    </wpfapplication2:chartitem>
    <wpfapplication2:chartitem name="应聘者b" color="#ff508bf3">
     <wpfapplication2:chartitem.datalist>
      <wpfapplication2:chartdata data="4"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="1"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="2"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="1"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="1"></wpfapplication2:chartdata>
     </wpfapplication2:chartitem.datalist>
    </wpfapplication2:chartitem>
    <wpfapplication2:chartitem name="应聘者c" color="#fff73131">
     <wpfapplication2:chartitem.datalist>
      <wpfapplication2:chartdata data="2"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="2"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="3"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="3"></wpfapplication2:chartdata>
      <wpfapplication2:chartdata data="3"></wpfapplication2:chartdata>
     </wpfapplication2:chartitem.datalist>
    </wpfapplication2:chartitem>
   </wpfapplication2:radarchartcontrol.datas>
  </wpfapplication2:radarchartcontrol>
 </grid>
</window>

以上所述是小编给大家介绍的wpf 自定义雷达图开发实例教程,希望对大家有所帮助