【C#】色分けして樹形図を描く（Bitmap）

# はじめに

1年ほど前に「[【C#】樹形図を描く（Bitmap）](https://qiita.com/nkojima/items/15b08211e369b30c3b5f)」という記事を書きましたが、この時は単色の樹形図を描くだけのプログラムでした。

別の記事の[【C#】ネコのイラストを描く（Bitmap）](https://qiita.com/nkojima/items/20d3e724f23ee29c7ac6)では様々な色を使って描画したので、今回は「樹形図の幹や枝部分を茶色、葉の部分を緑色に色分けして描画する」というのにチャレンジしてみました。

# 幹/枝と葉の峻別

* 今回は「樹形図の15回の枝分かれ」のうち「スタートから10回までを幹/枝として茶色で描画して、残りを葉として緑色で描画する」というルールにしました。

# 作成したコード

* 基本的なコードは、[【C#】樹形図を描く（Bitmap）](https://qiita.com/nkojima/items/15b08211e369b30c3b5f)に掲載されているコードと同じです。
* 今回修正したのは「幹/枝と葉を区別する部分」で、ColoredBranchオブジェクトを生成する際に"枝生成の残りの繰り返し回数"を使って「幹/枝と葉を区別するフラグ」をセットしました。
  * 実際にBitmapオブジェクトに描画する際に、このフラグを参照して色付けを行いました。

```csharp:ColoredTreeDrawer.cs
using System;
using System.Collections.Generic;
using System.Drawing;

namespace CSharpStudy.Image
{
    public class ColoredTreeDrawer
    {
        List<ColoredBranch> branches;

        /// <summary>
        /// 樹形図を描画するキャンバス。
        /// </summary>
        private Bitmap bmp;

        /// <summary>
        /// キャンバスサイズ。
        /// </summary>
        private int canvasSize;

        /// <summary>
        /// コンストラクタ。
        /// </summary>
        /// <param name="canvasSize">キャンバスの幅と高さ（px）</param>
        public ColoredTreeDrawer(int canvasSize = 300)
        {
            this.bmp = new Bitmap(canvasSize, canvasSize);
            this.canvasSize = canvasSize;
        }

        /// <summary>
        /// 樹形図を描画する。
        /// </summary>
        /// <param name="filePath">出力先のファイルパス。</param>
        public void Draw(string filePath)
        {
            // 樹形図の「枝」を作る。
            // 末端付近の枝（＝葉）だけ緑色にして、それ以外は茶色で描画する。
            branches = new List<ColoredBranch>();
            CreateBranch(15, this.canvasSize / 2, this.canvasSize, ToRadian(90), this.canvasSize / 5);

            using (Graphics g = Graphics.FromImage(bmp))
            {
                Pen brownPen = new Pen(Color.Brown, 1);
                Pen greenPen = new Pen(Color.Green, 1);

                foreach (var branch in branches)
                {
                    if (branch.IsLeaf)
                    {
                        g.DrawLine(greenPen, branch.StartPoint, branch.EndPoint);
                    }
                    else
                    {
                        g.DrawLine(brownPen, branch.StartPoint, branch.EndPoint);
                    }
                }
            }

            // 画像をPNG形式で保存する。
            bmp.Save(filePath, System.Drawing.Imaging.ImageFormat.Png);
        }

        /// <summary>
        /// 樹形図の枝を生成する。
        /// </summary>
        /// <param name="n">「枝生成」の残りの繰り返し回数</param>
        /// <param name="x1">枝の開始点のx座標</param>
        /// <param name="y1">枝の開始点のy座標</param>
        /// <param name="angle">枝の角度</param>
        /// <param name="length">枝の長さ</param>
        void CreateBranch(int n, double x1, double y1, double angle, double length)
        {
            if (n == 0) { return; }

            double x2 = x1 + length * Math.Cos(angle);
            double y2 = y1 - length * Math.Sin(angle);

            // 出力時の色をコントロールするため、末端付近の枝（＝葉）であるかのフラグをセットする。
            bool isLeaf = false;
            if (n<=5)
            {
                isLeaf = true;
            }

            var branch = new ColoredBranch(new Point((int)x1, (int)y1), new Point((int)x2, (int)y2), isLeaf);
            branches.Add(branch);

            CreateBranch(n - 1, x2, y2, angle - Math.PI / 10, length * 0.75);
            CreateBranch(n - 1, x2, y2, angle + Math.PI / 10, length * 0.75);
        }

        /// <summary>
        /// 角度の「度」をラジアンにして返す。
        /// </summary>
        /// <param name="angle">角度の「度」</param>
        /// <returns>ラジアン</returns>
        double ToRadian(double angle)
        {
            return angle * Math.PI / 180;
        }
    }

    /// <summary>
    /// 「枝」を表すクラス。
    /// </summary>
    class ColoredBranch
    {
        /// <summary>
        /// 枝の開始点
        /// </summary>
        public Point StartPoint { get; }

        /// <summary>
        /// 枝の終了点
        /// </summary>
        public Point EndPoint { get; }

        /// <summary>
        /// この枝が葉（末端付近）であるか？
        /// </summary>
        public bool IsLeaf { get; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="start">枝の開始点</param>
        /// <param name="end">枝の終了点</param>
        /// <param name="isLeaf">この枝が葉（＝末端付近）であるか？</param>
        public ColoredBranch(Point startPoint, Point endPoint, bool isLeaf)
        {
            StartPoint = startPoint;
            EndPoint = endPoint;
            IsLeaf = isLeaf;
        }
    }
}
```

# 実行用のコード

```csharp:ColoredTreeDrawerTest
using CSharpStudy.Image;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CSharpStudyTest
{
    [TestClass]
    public class ColoredTreeDrawerTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            ColoredTreeDrawer td = new ColoredTreeDrawer(400);
            td.Draw(@".\colored_tree.png");
        }
    }
}
```

# 生成された画像

* 幹/枝と葉が色分けされているので、葉の部分だけは木の雰囲気が出てきました。
* 一方で幹/枝/葉の線の太さが同じなので、全体としてどうしても不自然な感じになってしまいました。

![colored_tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312166/630b37cf-1185-cfd7-755a-4c3077ed42cb.png)


# 参考URL

* [【C#】樹形図を描く（Bitmap）](https://qiita.com/nkojima/items/15b08211e369b30c3b5f)
* [【C#】ネコのイラストを描く（Bitmap）](https://qiita.com/nkojima/items/20d3e724f23ee29c7ac6)