效果
明細用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
2017-07-13更新:
昨天剛發了博客,今天就發現了bug,真尷尬.180-270度和270-360度的算法有問題,由于例子選用尺寸的問題,早時沒有發現.
正確的算法:
180-270度:
X軸:1.向左移動一個Popup的Width 2.向右移動一個(r-b)的距離 3.向右移動二分之一個ArcThickness的距離
Y軸不變
270-360度:
X軸:1.向左移動一個Popup的Width 2.向右移動一個(r-a)的距離 3.向右移動二分之一個ArcThickness的距離
Y軸不變
源碼已更新,歡迎重新下載
文章列表