基于WPF实现带明细的环形图表
作者:普通的地球人 发布时间:2021-12-01 03:58:11
效果
明细用Popup实现的,录gif时,Popup显示不出来,不知道为什么,所以静态图凑合看吧
大体思路
图表使用Arc+Popup实现
图表分为两部分,一是环形部分,一是标注的明细部分.
环形部分使用Arc图形表示.需要注意这个Arc是Blend里的图形.用Blend建项目的话可以直接用,使用VS建项目需要添加引用 Microsoft.Expression.Drawing 在引用管理器=>程序集=>扩展 下(前提是已经安装了Blend)
明细部分使用Popup控件,IsOpen属性绑定到Arc的IsMouseOver,也就是鼠标进入圆弧的时候,Popup就打开显示.
Popup内部一个椭圆控件当作背景,一个文字显示,一个折线虚线化当作指针
然后就是把Popup定位到对应圆弧合适的位置去显示(这里取的是圆弧的中间)
比较抱歉的是样式比较丑陋,忽略吧,重点看定位.
圆弧部分
Arc有两个重要的属性:StartAngle起始角度和EndAngle终结角度.这两个属性决定了圆弧占所在圆环的比例.
每一个数据项就对应一个圆弧,把所有圆弧都放到一个容器里,首尾相连
数据项的总和为100,那么所有圆弧也就组成一个完整的圆环.
Popup明细部分
明细部分分为四种,见图
椭圆
从图可知,作为背景的椭圆分为两种情况,小于180度,椭圆靠容器的右边对齐,大于180度,靠容器的左边对齐
也就是代码的这部分:
Ellipse ell = new Ellipse() { Fill = brush };
//中间点角度小于180 明细靠右显示 否则靠左显示
Grid detailGrid = new Grid() { Width = _popupHeight, HorizontalAlignment = HorizontalAlignment.Right };
if (middleAngle > 180)
{
detailGrid.HorizontalAlignment = HorizontalAlignment.Left;
}
折线
折线是分为四种,每一个角度区间都对应一种
private Polyline GetPopupPolyline(double middleAngle)
{
Polyline pLine = new Polyline() { Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 0)), StrokeDashArray = new DoubleCollection(new double[] { 5, 2 }) };
double x1 = 0, y1 = 0;
double x2 = 0, y2 = 0;
double x3 = 0, y3 = 0;
if (middleAngle > 0 && middleAngle <= 90)
{
x1 = 0; y1 = _popupHeight;
x2 = _popupWidth / 2; y2 = _popupHeight;
x3 = _popupWidth * 3 / 4; y3 = _popupHeight / 2;
}
if (middleAngle > 90 && middleAngle <= 180)
{
x1 = 0; y1 = 0;
x2 = _popupWidth / 2; y2 = 0;
x3 = _popupWidth * 3 / 4; y3 = _popupHeight / 2;
}
if (middleAngle > 180 && middleAngle <= 270)
{
x1 = _popupWidth; y1 = 0;
x2 = _popupWidth / 2; y2 = 0;
x3 = _popupWidth / 4; y3 = _popupHeight / 2;
}
if (middleAngle > 270 && middleAngle <= 360)
{
x1 = _popupWidth; y1 = _popupHeight;
x2 = _popupWidth / 2; y2 = _popupHeight;
x3 = _popupWidth / 4; y3 = _popupHeight / 2;
}
pLine.Points.Add(new Point(x1, y1));
pLine.Points.Add(new Point(x2, y2));
pLine.Points.Add(new Point(x3, y3));
return pLine;
}
Popup的定位
首先以0-90度为例,说明一些基本的东西,见图
首先Popup默认的位置,都是在它容器的左下方的,Popup的左上角和容器的左下角重合.
现在要做的是Popup标记为红点的位置,和圆环上标记为红点的位置重合.
先来回顾一下小时候学过的公式:
1.直角三角形 a=r*sinA
2.勾股定理 c^2=a^2+b^2 b=Sqrt(c^2-a^2)
上图的直角三角形,角A的对边为a,临边为b,斜边为c.显然c边于圆的半径r相等.注意:因为圆弧是有厚度的,所以取r的时候要减去二分之一的圆弧厚度.
角A是可以通过90度减去圆弧的对应的角度求出来的,也就是sinA的值已知了,那么就可以求出a和b的长度,然后就可以去移动Popup了
一.0-90度
X轴:1.向右移动二分之一个容器的width 2.向右移动一个b的距离
Y轴:1.向上移动二分之一个容器的height 2.向上移动一个Popup的height 3.向上移动一个a的距离
二.90-180度
X轴:1.向右移动二分之一个容器的width 2.向右移动一个a的距离
Y轴:1.上移二分之一个圆弧的Thickness,以保证标记的起点在圆弧的中央 2.上移一个(r-b)的距离
三.180-270度
X轴:1.向左移动一个b的距离
Y轴:1.上移二分之一个圆弧的Thickness,以保证标记的起点在圆弧的中央 2.上移一个(r-a)的距离
四.270-360度
X轴:1.向左移动一个a的距离
Y轴:1.向上移动二分之一个容器的height 2.向上移动一个Popup的height 3.向上移动一个b的距离
代码部分
private Popup GetPopup(double middleAngle)
{
/*
* 生成popup
* 设置popup的offset 让标记线的起点 对应到圆弧的中间点
*/
Popup popup = new Popup() { Width = _popupWidth, Height = _popupHeight, AllowsTransparency = true, IsHitTestVisible = false };
//直角三角形 a=r*sinA 勾股定理 c^2=a^2+b^2 b=Sqrt(c^2-a^2)
double r = _chartSize / 2 - _arcThickness / 2;
double offsetX = 0, offsetY = 0;
if (middleAngle > 0 && middleAngle <= 90)
{
double sinA = Math.Sin(Math.PI * (90 - middleAngle) / 180);
double a = r * sinA;
double c = r;
double b = Math.Sqrt(c * c - a * a);
offsetX = _chartSize / 2 + b;
offsetY = -(_chartSize / 2 + _popupHeight + a);
}
if (middleAngle > 90 && middleAngle <= 180)
{
double sinA = Math.Sin(Math.PI * (180 - middleAngle) / 180);
double a = r * sinA;
double c = r;
double b = Math.Sqrt(c * c - a * a);
offsetX = _chartSize / 2 + a;
offsetY = -(_arcThickness / 2 + (r - b));
}
if (middleAngle > 180 && middleAngle <= 270)
{
double sinA = Math.Sin(Math.PI * (270 - middleAngle) / 180);
double a = r * sinA;
double c = r;
double b = Math.Sqrt(c * c - a * a);
offsetX = -_popupWidth + (r - b) + _arcThickness / 2;
offsetY = -(_arcThickness / 2 + (r - a));
}
if (middleAngle > 270 && middleAngle <= 360)
{
double sinA = Math.Sin(Math.PI * (360 - middleAngle) / 180);
double a = r * sinA;
double c = r;
double b = Math.Sqrt(c * c - a * a);
offsetX = -_popupWidth + (r - a) + _arcThickness / 2;
offsetY = -(_chartSize / 2 + _popupHeight + b);
}
popup.HorizontalOffset = offsetX;
popup.VerticalOffset = offsetY;
return popup;
}
差不多主要的就是这些了.到这.画图有点累.
源码下载:ArcChart.zip
来源:https://www.cnblogs.com/tsliwei/p/7155616.html


猜你喜欢
- 在实际业务中,当后台数据发生变化,客户端能够实时的收到通知,而不是由用户主动的进行页面刷新才能查看,这将是一个非常人性化的设计。有没有那么一
- 前言:在java的网络通信中,两个不同节点的主机想要进行通信则可以通过建立Socket对象(相当于客户端主机,向服务端请求发送信息)和Ser
- 本文实例讲述了Android开发中Launcher3常见默认配置修改方法。分享给大家供大家参考,具体如下:Launcher概述Launche
- 前言空间分配要点有:一是空间分配的连续性;二是动态内存申请;三是防止程序执行中出现异常错误。提示:开始讲解了嗷~后续会根据精力持续更新嗷!!
- 本文为大家分享了经典24点纸牌益智游戏的具体实现方法,供大家参考,具体内容如下一.实验内容24点游戏是经典的纸牌益智游戏。常见游戏规则:从扑
- 人人客户端有一个特效还是挺吸引人的,在主界面手指向右滑动,就可以将菜单展示出来,而主界面会被隐藏大部分,但是仍有左侧的一小部分同菜单一起展示
- 本文示例实现了Android退出时关闭所有Activity的功能,分享给大家供大家参考之用。具体方法如下:一般来说,在Android退出时,
- 本文实例为大家分享了Android绘制仪表盘指针刻度的具体代码,供大家参考,具体内容如下不废话,先看效果图:表盘的绘制重点有两点:1.表盘刻
- tasks下面的代码展示了三个Gradle task,稍后会讲解这三者的不同。 task myTask { println "He
- 本文实例总结了C#实现按照指定长度在数字前补0方法。分享给大家供大家参考。具体分析如下:这里分析了C#按照指定的长度在数字前补0的两种方法例
- 项目中有几个batch需要检查所有的用户参与的活动的状态,以前是使用分页,一页一页的查出来到内存再处理,但是随着数据量的增加,效率越来越低。
- 从今天开始写关于C#的系列文章,本篇文章主要讲解C#中的委托使用。委托其实就是一种数据类型,和int,string是一样的概念。如果要把一个
- 在开发 Web 项目的时候,经常需要过滤器来处理一些请求,包括字符集转换什么的,记录请求日志什么的等等。在之前的 Web 开发中,我们习惯把
- idea去掉不想commit的文件我们项目在每次commit代码时,有时候会有一些不想提交又不能删除的代码,怎么做呢?此方法亲测最方便!!!
- 即只能在组件布局代码后,或者在组件的前面添加注释。#注释格式:Android的XML文件注释一般采用 <!--注释内容 -->的
- System.ComponentModel.Design.DesignSurface是为设计组件提供一个用户界面,通过它可以实现一个简单的窗
- 在IntelliJ IDEA 中这个查看一个类也就是当前类的所有继承关系,包括实现的所有的接口和继承的类,这个继承,不仅仅是一级的继承关系,
- 场景描述单例模式对于我们来说一点也不模式,是一个常见的名称,单例模式在程序中的实际效果就是:确保一个程序中只有一个实例,并提供一个全局访问点
- 概述从之前的配置里面我们可以看到我们的 URL 都是写死的,这不符合我们微服务的要求,我们微服务是只要知道服务的名字,根据名字去找,而直接写
- //======================================//输出格式: hex2bin 5e.//得到: 0101