午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPFで図形の円弧🌙、🍕扇形パイ形、🍩ドーナツ型(アーチ形)を表示してみた、ArcSegment

ArcSegmentを使って円弧🌙、🍕パイ形、🍩ドーナツ型(アーチ形)を表示してみた
イメージ 1
名前は円弧と扇形はいいと思うけど
ドーナツ型なのかアーチ形なのか、また別の名前があるのかも
でも🍩がいいなあってことで関数名はDonutにした
デザイン画面、MainWindow.xaml

f:id:gogowaten:20191214103519p:plain

 
イメージ 3
MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
//c# - WPF Doughnut ProgressBar - Stack Overflow
//https://stackoverflow.com/questions/36752183/wpf-doughnut-progressbar

namespace _20190331_円弧arcSegment
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            double radian = 50;//半径 
            Point center = new Point(radian, radian);//中心点
            double distance = 50;//中心点からの距離
            PathGeometry arcPathGeo;//円弧のPathGeometry


            //   時計回りに0~250度の円弧
            //中心点座標が半径と同じ、中心からの距離も半径と同じ
            arcPathGeo = ArcGeometry(center, distance, 0, 250, SweepDirection.Clockwise);
            MyStackPanel1.Children.Add(MakePath(arcPathGeo));

            //中心点座標が半径と同じ、中心からの距離が25
            arcPathGeo = ArcGeometry(center, 25, 0, 250, SweepDirection.Clockwise);
            MyStackPanel1.Children.Add(MakePath(arcPathGeo));

            //中心点座標が(100,100)、中心からの距離は半径と同じ
            arcPathGeo = ArcGeometry(new Point(100, 100), distance, 0, 250, SweepDirection.Clockwise);
            MyStackPanel1.Children.Add(MakePath(arcPathGeo));

            //中心点座標が(100,100)、中心からの距離が25
            arcPathGeo = ArcGeometry(new Point(100, 100), 25, 0, 250, SweepDirection.Clockwise);
            MyStackPanel1.Children.Add(MakePath(arcPathGeo));


            //反時計回りに100~330度
            arcPathGeo = ArcGeometry(center, radian, 100, 330, SweepDirection.Counterclockwise);
            MyStackPanel1.Children.Add(MakePath(arcPathGeo));


            //扇形、🍕
            PathGeometry piePahtGeo;
            piePahtGeo = PieGeometry(center, distance, 330, 30, SweepDirection.Clockwise);
            MyStackPanel1.Children.Add(MakePath(piePahtGeo));

            piePahtGeo = PieGeometry(center, distance, 330, 30, SweepDirection.Counterclockwise);
            MyStackPanel1.Children.Add(MakePath(piePahtGeo));


            //🍩
            PathGeometry donut;
            donut = DonutGeometry(center, 20, distance, 20, 300, SweepDirection.Clockwise);
            MyStackPanel1.Children.Add(MakePath(donut));

            donut = DonutGeometry(center, 20, distance, 20, 300, SweepDirection.Counterclockwise);
            MyStackPanel1.Children.Add(MakePath(donut));

        }

        private Path MakePath(PathGeometry geo)
        {
            Path path;
            path = new Path
            {
                Data = geo,
                Stroke = Brushes.PaleVioletRed,
                StrokeThickness = 4,
                Margin = new Thickness(4)
            };
            return path;
        }
        /// <summary>
        /// ドーナツ形、アーチ形のPathGeometry作成
        /// </summary>
        /// <param name="center">中心座標</param>
        /// <param name="width"></param>
        /// <param name="distance">中心からの距離</param>
        /// <param name="startDeg">開始角度、0以上360未満</param>
        /// <param name="stopDeg">終了角度、0以上360未満</param>
        /// <param name="direction">回転方向、clockwiseが時計回り</param>
        /// <returns></returns>
        private PathGeometry DonutGeometry(Point center, double width, double distance, double startDeg, double stopDeg, SweepDirection direction)
        {
            //外側の円弧終始点
            Point outSideStart = MakePoint(startDeg, center, distance);
            Point outSideStop = MakePoint(stopDeg, center, distance);

            //内側の円弧終始点は角度と回転方向が外側とは逆になる
            Point inSideStart = MakePoint(stopDeg, center, distance - width);
            Point inSideStop = MakePoint(startDeg, center, distance - width);

            //開始角度から終了角度までが180度を超えているかの判定
            //超えていたらArcSegmentのIsLargeArcをtrue、なければfalseで作成
            double diffDegrees = (direction == SweepDirection.Clockwise) ? stopDeg - startDeg : startDeg - stopDeg;
            if (diffDegrees < 0) { diffDegrees += 360.0; }
            bool isLarge = (diffDegrees > 180) ? true : false;

            //arcSegment作成
            var outSideArc = new ArcSegment(outSideStop, new Size(distance, distance), 0, isLarge, direction, true);
            //内側のarcSegmentは回転方向を逆で作成
            var inDirection = (direction == SweepDirection.Clockwise) ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
            var inSideArc = new ArcSegment(inSideStop, new Size(distance - width, distance - width), 0, isLarge, inDirection, true);

            //PathFigure作成、外側から内側で作成している
            //2つのarcSegmentは、2本の直線(LineSegment)で繋げる
            var fig = new PathFigure();
            fig.StartPoint = outSideStart;
            fig.Segments.Add(outSideArc);
            fig.Segments.Add(new LineSegment(inSideStart, true));//外側終点から内側始点への直線
            fig.Segments.Add(inSideArc);
            fig.Segments.Add(new LineSegment(outSideStart, true));//内側終点から外側始点への直線
            fig.IsClosed = true;//Pathを閉じる必須

            var pg = new PathGeometry();
            pg.Figures.Add(fig);
            return pg;
        }
        /// <summary>
        /// 円弧のPathGeometryを作成
        /// </summary>
        /// <param name="center">中心座標</param>
        /// <param name="distance">中心点からの距離(半径)</param>
        /// <param name="startDegrees">開始角度、0以上360未満で指定</param>
        /// <param name="stopDegrees">終了角度、0以上360未満で指定</param>
        /// <param name="direction">回転方向、Clockwiseが時計回り</param>
        /// <returns></returns>
        private PathGeometry ArcGeometry(Point center, double distance, double startDegrees, double stopDegrees, SweepDirection direction)
        {
            Point stop = MakePoint(stopDegrees, center, distance);//終点座標

            //IsLargeの判定、
            //開始角度から終了角度までが180度を超えていたらtrue、なければfalse
            double diffDegrees = (direction == SweepDirection.Clockwise) ? stopDegrees - startDegrees : startDegrees - stopDegrees;
            if (diffDegrees < 0) { diffDegrees += 360.0; }
            bool isLarge = (diffDegrees > 180) ? true : false;

            //ArcSegment作成
            var arc = new ArcSegment(stop, new Size(distance, distance), 0, isLarge, direction, true);

            //PathFigure作成
            var fig = new PathFigure();
            Point start = MakePoint(startDegrees, center, distance);//始点座標
            fig.StartPoint = start;//始点座標をスタート地点に
            fig.Segments.Add(arc);//ArcSegment追加

            //PathGeometry作成、PathFigure追加
            var pg = new PathGeometry();
            pg.Figures.Add(fig);
            return pg;
        }


        //完成形、回転方向を指定できるように
        /// <summary>
        /// 扇(pie)型のPathGeometryを作成
        /// </summary>
        /// <param name="center">中心座標</param>
        /// <param name="distance">中心点からの距離</param>
        /// <param name="startDegrees">開始角度、0以上360未満で指定</param>
        /// <param name="stopDegrees">終了角度、0以上360未満で指定</param>
        /// <param name="direction">回転方向、Clockwiseが時計回り</param>
        /// <returns></returns>
        private PathGeometry PieGeometry(Point center, double distance, double startDegrees, double stopDegrees, SweepDirection direction)
        {
            Point start = MakePoint(startDegrees, center, distance);//始点座標
            Point stop = MakePoint(stopDegrees, center, distance);//終点座標
            //開始角度から終了角度までが180度を超えているかの判定
            //超えていたらArcSegmentのIsLargeArcをtrue、なければfalseで作成
            double diffDegrees = (direction == SweepDirection.Clockwise) ? stopDegrees - startDegrees : startDegrees - stopDegrees;
            if (diffDegrees < 0) { diffDegrees += 360.0; }
            bool isLarge = (diffDegrees > 180) ? true : false;
            var arc = new ArcSegment(stop, new Size(distance, distance), 0, isLarge, direction, true);

            //PathFigure作成
            //ArcSegmentとその両端と中心点をつなぐ直線LineSegment
            var fig = new PathFigure();
            fig.StartPoint = start;//始点座標
            fig.Segments.Add(arc);//ArcSegment追加
            fig.Segments.Add(new LineSegment(center, true));//円弧の終点から中心への直線
            fig.Segments.Add(new LineSegment(start, true));//中心から円弧の始点への直線
            fig.IsClosed = true;//Pathを閉じる、必須

            //PathGeometryを作成してPathFigureを追加して完成
            var pg = new PathGeometry();
            pg.Figures.Add(fig);
            return pg;
        }

        /// <summary>
        /// 距離と角度からその座標を返す
        /// </summary>
        /// <param name="degrees">360以上は359.99になる</param>
        /// <param name="center">中心点</param>
        /// <param name="distance">中心点からの距離</param>
        /// <returns></returns>
        private Point MakePoint(double degrees, Point center, double distance)
        {
            if (degrees >= 360) { degrees = 359.99; }
            var rad = Radian(degrees);
            var cos = Math.Cos(rad);
            var sin = Math.Sin(rad);
            var x = center.X + cos * distance;
            var y = center.Y + sin * distance;
            return new Point(x, y);
        }
        private double Radian(double degree)
        {
            return Math.PI / 180.0 * degree;
        }

    }
}
 
 
 
 
ArcSegment
 
public ArcSegment (
	point,		円弧の終点
	size,			円弧の半径
	rotationAngle,	回転?今回は0で固定
	isLargeArc,	円弧が180度を超えるかどうか
	sweepDirection,	Clockwiseが時計回り、Counterclockwiseが反時計回り
	isStroked		描くかどうか、今回はtrueで固定);
 
このArcSegmentを使って
円弧のPathGeometryを作成するArcGeometry

f:id:gogowaten:20191214103744p:plain

指定するのは
中心座標、
中心座標からの距離(半径)、
開始角度、
終了角度、
回転方向
 
 
半径 50
中心座標(50,50)
開始角度 0
終了角度 250
回転方向 時計回り
を指定したとき
イメージ 4
こうなればいい
イメージ 5
左上が(0,0)、半径が50なので中心座標は(50,50)
 
 
イメージ 6
 
 
ArcSegmentには始点と終点の座標が必要なので
それを求めるMakePoint
イメージ 7
終点座標
角度250、中心座標(50,50)、距離は半径と同じ50
を渡すと
 
イメージ 8
円弧の終点は(32.9,3.0)
 
同じように円弧の始点も計算
イメージ 9
開始角度は0だから真横に半径のぶん移動しただけの(100,50)
イメージ 10
始点と終点座標
 
ArcSegment作成
作成時に渡すパラメータをもう一度見ると
public ArcSegment (
	point,		円弧の終点
	size,			円弧の半径
	rotationAngle,	回転?今回は0で固定
	isLargeArc,	円弧が180度を超えるかどうか
	sweepDirection,	Clockwiseが時計回り、Counterclockwiseが反時計回り
	isStroked		描くかどうか、今回はtrueで固定);
Pointは終点なので(32.9,3.0)
sizeは半径の50でおk
sweepDirectionは時計回りなのでClockwise
あとは
IsLargeは回転角度は250-0=250で180以上なのでtrue
143行目~

f:id:gogowaten:20191214103816p:plain

SweepDirection回転方向によって引き算の向きを変えて計算して
マイナスだったら360足した値で判定している
これは冗長な気もするけどわからん
 
開始が30で終了が200で
時計回りの場合
  200-30=170は180以上なのでIsLargeはfalse
反時計回りの場合
  30-200=-170
  -170+360=190は180以上なのでIsLargeをtrue
 
 
 
これでArcSegmentを作成できる
f:id:gogowaten:20191214103835p:plain
ArcSegment作成できたので、それを入れるPathFigure作成

PathFigure作成
イメージ 13
PathFigureのStartPointにはさっき求めた始点座標
PathFigureのSegmentsにArcSegmentを追加
 
PathGeometry作成
イメージ 15
PathGeometryのFiguresにPathFigureを追加して完成、やっとできた
 
 
 

f:id:gogowaten:20191214103852p:plain

これでで円弧のPathGeometryができたので、27行目
 
 
イメージ 17
あとはPathを作って、そのDataに指定して表示
 
 
パイ型🍕のPathGeometry作成
イメージ 18
円弧の両端に中心座標から伸びる直線
LineSegmentを足せばいい
 
 
ArcSegmentを作成するまでは円弧作成と全く同じなので
PathFigureにSegmentを加えていくところから
イメージ 19

191行目まで全く同じ、ここまでだと円弧の状態

192行目で円弧の終点から中心への直線を追加、ここまでだと
イメージ 20
終点から中心への直線追加
 
イメージ 21
中心から円弧の始点への直線追加、193行目
 
最後にPathを閉じると194行目
イメージ 22
きれいにつながる
 
 
ドーナツ型🍩
イメージ 23
🍕(パイ型)とほとんど同じ
2つの円弧ArcSegmentと直線のLineSegmentで作成
一筆書きで作ったので2つの円弧の開始角度と終了角度が逆になる
外側の円弧から開始
外側の円弧
外側の円弧の終点から内側の円弧の始点への直線
内側の円弧
内側の円弧の終点から外側の円弧の始点への直線
Pathを閉じて完了
 
20度から300度、時計回り
 
 
イメージ 24
distanceは距離だけど半径みたいなもの
widthは外側と内側の差
内側の円弧は開始角度と終了角度が逆になるのと
中心点からの距離がwidthのぶん短くなる、100,101行目
 
イメージ 25
IsLargeの判定や外側のArcSegment作成も普通の円弧と同じ、105~110行目
内側のArcSegmentは回転方向が逆にして、112行目
sizeもwidth幅のぶんだけ小さくして作成、113行目
 
 
イメージ 26
外側の円弧、直線、内側の円弧、直線の順番で繋げる
 
イメージ 27
 
 
イメージ 28
//ハ゜ックマン
System.Windows.Controls.WrapPanel wrap = new System.Windows.Controls.WrapPanel();
MyStackPanel1.Children.Add(wrap);
Path pacman = new Path();
pacman.Data = PieGeometry(new Point(100, 100), 100, 30, 330, SweepDirection.Clockwise);
pacman.Fill = Brushes.Yellow;
wrap.Children.Add(pacman);
MyStackPanel1.Background = Brushes.Black;

//エサ
for (int i = 0; i < 3; i++)
{
  Path esa = new Path();
  esa.Data = new EllipseGeometry(new Rect(new Size(20, 20)));
  esa.Fill = Brushes.Yellow;
  esa.Margin = new Thickness(-20, 0, 100, 0);
  esa.VerticalAlignment = VerticalAlignment.Center;
  wrap.Children.Add(esa);
}
 
 

 
参照したところ
c# - WPF Doughnut ProgressBar - Stack Overflow
https://stackoverflow.com/questions/36752183/wpf-doughnut-progressbar
ここがなかったら今回のはできなかった
 
 
WPFにはEllipseGeometryとRectangleGeometryがあるから円と四角形は簡単に表示できるだけどねえ
円弧にはArcSegmentがあるけどこれがさっぱりわからん
ぐぐったらさっきのリンク先がわかりやすかったので助かった
これで
中心点座標と半径、開始角度、終了角度、回転方向を指定して円弧を表示することができた
もっと省略するなら中心点座標と回転方向は要らないかな、回転方向は固定でいいし中心もあとからオフセットすれば良さそう
 
 
ギットハブ
 
 
関連記事
2019/04/04は明日
画像の色相を円形ヒストグラム、扇形(パイ型)グラフで表示するアプリできた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15923169.html
イメージ 29