エンジニア

なんくるないさ

「このブログはアフィリエイト広告を利用しています」

Unityでグラフを書く(折れ線偏)

今回はUnityでグラフをつくります(折れ線グラフ)

いろいろ探した結果,有料のグラフがでできたのですが
それは違うと思い、無料でグラフを作る方法を調べました 1日もかからずできます。 unityでグラフを書こうとしている人は参考に!

この動画を参考にしました


Unity Tutorial - Create a Graph

それを参考にして作ったグラフがこちら

f:id:jump1268:20190812144954p:plain

f:id:jump1268:20190812144956p:plain

それが組み込まれたアプリケーション play.google.com

普段わからない顔の大きさをAIを使って数値化するアプリです。日々顔の大きさを記録していき、ダイエットのモチベーションを高めます。

プログラムの全貌

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using CodeMonkey.Utils;
using System;
using System.Linq;

public class Window_Graph : MonoBehaviour
{
    [SerializeField] private Sprite circleSprite;
    private RectTransform graphContainer;
    private RectTransform labelTemplateX;
    private RectTransform labelTemplateY;
    private RectTransform dashTemplateX;
    private RectTransform dashTemplateY;

    public Vector3 start;
    public Vector3 end;
    public float deltaX = 0f;


    private void Awake()
    {
        graphContainer = transform.Find("graphContainer").GetComponent<RectTransform>();
        labelTemplateX = graphContainer.Find("labelTemplateX").GetComponent<RectTransform>();
        labelTemplateY = graphContainer.Find("labelTemplateY").GetComponent<RectTransform>();
        dashTemplateX = graphContainer.Find("dashTemplateX").GetComponent<RectTransform>();
        dashTemplateY = graphContainer.Find("dashTemplateY").GetComponent<RectTransform>();


        // スコアのロード
        var score_num = PlayerPrefs.GetInt("SCORE", 0);
        List<float> face_num = new List<float>();
        for (int i = 0; i < 30; i++)
        {
            var data = (PlayerPrefs.GetFloat("Face" + i, 0));
            if (data > 150)
            {
                face_num.Add(data);
            }
        }
        ShowGraph(face_num, (int _i) => "Day" + (_i + 1), (float _f) => Mathf.RoundToInt(_f)+"cm^2") ;
    }

    private GameObject CreateCircle(Vector2 anchoredPosition)
    {
        GameObject gameObject = new GameObject("circle", typeof(Image));
        gameObject.transform.SetParent(graphContainer, false);
        gameObject.GetComponent<Image>().sprite = circleSprite;
        RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
        rectTransform.anchoredPosition = anchoredPosition;
        rectTransform.sizeDelta = new Vector2(11, 11);
        rectTransform.anchorMin = new Vector2(0, 0);
        rectTransform.anchorMax = new Vector2(0, 0);
        return gameObject;
    }

    private void ShowGraph(List<float> valueList, Func<int, string> getAxisLabelX=null, Func<float, string> getAxisLabelY=null)
    {
        if (getAxisLabelX == null)
        {
            getAxisLabelX = delegate (int _i) { return _i.ToString(); };
        }
        if (getAxisLabelY == null)
        {
            getAxisLabelY = delegate (float _f) { return Mathf.RoundToInt(_f).ToString(); };
        }
        float graphHeight = graphContainer.sizeDelta.y;
        float graphWidth = graphContainer.sizeDelta.x;




        float yMinimum = valueList[0];
        float yMaximum = valueList[0];
        foreach (int value in valueList)
        {
            if (value > yMaximum)
            {
                yMaximum = value;
            }
            if (value < yMinimum)
            {
                yMinimum = value;
            }
        }
        yMaximum = yMaximum + ((yMaximum-yMinimum)*0.2f);
        yMinimum = yMinimum - ((yMaximum-yMinimum)* 0.2f);

        float xSize = graphWidth/15;

        GameObject lastCircleGameObject = null;
        for (int i = 0; i<valueList.Count; i++)
        {
            float xPosition = xSize+ i * xSize;
            float yPosition = ((valueList[i]-yMinimum) / (yMaximum-yMinimum)) * graphHeight;
            GameObject circleGameObject =  CreateCircle((new Vector2(xPosition, yPosition)));
            if(lastCircleGameObject != null)
            {
                CreateDotConnection(lastCircleGameObject.GetComponent<RectTransform>().anchoredPosition, circleGameObject.GetComponent<RectTransform>().anchoredPosition);
            }
            lastCircleGameObject = circleGameObject;

            RectTransform labelX = Instantiate(labelTemplateX);
            labelX.SetParent(graphContainer,false);
            labelX.gameObject.SetActive(true);
            labelX.anchoredPosition = new Vector2(xPosition, -7f);
            labelX.GetComponent<Text>().text = getAxisLabelX(i);


            RectTransform dashX = Instantiate(dashTemplateY);
            dashX.SetParent(graphContainer, false);
            dashX.gameObject.SetActive(true);
            dashX.anchoredPosition = new Vector2(xPosition, -7f);

        }
        int separatorCount = 10;
        for (int i = 0; i <= separatorCount; i++)
        {
            RectTransform labelY = Instantiate(labelTemplateY);
            labelY.SetParent(graphContainer, false);
            labelY.gameObject.SetActive(true);
            float normalizedValue = i * 1f / separatorCount;
            labelY.anchoredPosition = new Vector2(-7f, normalizedValue * graphHeight);
            labelY.GetComponent<Text>().text = getAxisLabelY(yMinimum+( normalizedValue * (yMaximum-yMinimum)));



            RectTransform dashY = Instantiate(dashTemplateX);
            dashY.SetParent(graphContainer, false);
            dashY.gameObject.SetActive(true);
            dashY.anchoredPosition = new Vector2(-7f, normalizedValue * graphHeight);

        }


    }

    private void CreateDotConnection(Vector2 dotPositionA, Vector2 dotPostionB)
    {
        GameObject gameObject = new GameObject("dotConnection", typeof(Image));
        gameObject.transform.SetParent(graphContainer, false);
        gameObject.GetComponent<Image>().color = new Color(0, 1, 0, 1f);

        RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
        Vector2 dir = (dotPostionB - dotPositionA).normalized;
        float distance = Vector2.Distance(dotPositionA, dotPostionB);
        
        rectTransform.anchorMin = new Vector2(0, 0);
        rectTransform.anchorMax = new Vector2(0, 0);
        rectTransform.sizeDelta = new Vector2(distance, 3f);
        rectTransform.anchoredPosition = dotPositionA +dir*distance*0.5f ;
        rectTransform.localEulerAngles = new Vector3(0, 0, UtilsClass.GetAngleFromVector(dir));
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
    }
}

解説

配置したgraphContainerの中に作ったlabelと破線を取得します

graphContainer = transform.Find("graphContainer").GetComponent<RectTransform>();
labelTemplateX = graphContainer.Find("labelTemplateX").GetComponent<RectTransform>();
labelTemplateY = graphContainer.Find("labelTemplateY").GetComponent<RectTransform>();
dashTemplateX = graphContainer.Find("dashTemplateX").GetComponent<RectTransform>();
dashTemplateY = graphContainer.Find("dashTemplateY").GetComponent<RectTransform>();

PlayerPrefsでスマホ内部に保存したデータを1つずつ読み リストに追加します。 そのリストをshowgraph関数に渡すことによりグラフを描画します

showgraphの引数
(int i) => "Day" + (i + 1)は Day1,Day2みたいにxのラベルを決める部分です

        // スコアのロード
        var score_num = PlayerPrefs.GetInt("SCORE", 0);
        List<float> face_num = new List<float>();
        for (int i = 0; i < 30; i++)
        {
            var data = (PlayerPrefs.GetFloat("Face" + i, 0));
            if (data > 150)
            {
                face_num.Add(data);
            }
        }
        ShowGraph(face_num, (int _i) => "Day" + (_i + 1), (float _f) => Mathf.RoundToInt(_f)+"cm^2") ;

s GameObjectを作るスクリプト
GameObject gameObject = new GameObject("circle", typeof(Image));
とすることで、新しくGameObjectを作るのですが、component(今回の場合はImage)をアタッチできます
AddComponent();とする手間が省けます

作ったGameObjectの親をgraphContainerとしますが、
この時は親の位置や状態を気にせずローカル座標系で考えます?
gameObject.transform.SetParent(graphContainer, false);

    private GameObject CreateCircle(Vector2 anchoredPosition)
    {
        GameObject gameObject = new GameObject("circle", typeof(Image));
        gameObject.transform.SetParent(graphContainer, false);
        gameObject.GetComponent<Image>().sprite = circleSprite;
        RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
        rectTransform.anchoredPosition = anchoredPosition;
        rectTransform.sizeDelta = new Vector2(11, 11);
        rectTransform.anchorMin = new Vector2(0, 0);
        rectTransform.anchorMax = new Vector2(0, 0);
        return gameObject;
    }

一番需要なグラフをかく関数
Func<代入する型, 戻り値の型> func = (引数) => { 実行したいメソッド };

引数に関数の戻り値を指定してある ShowGraph(face_num, (int i) => "Day" + (i + 1), (float f) => Mathf.RoundToInt(f)+"cm2") ;

ymax,yminの2割の場所をグラフの描写エリアとします。

  private void ShowGraph(List<float> valueList, Func<int, string> getAxisLabelX=null, Func<float, string> getAxisLabelY=null)
    {
        if (getAxisLabelX == null)
        {
            getAxisLabelX = delegate (int _i) { return _i.ToString(); };
        }
        if (getAxisLabelY == null)
        {
            getAxisLabelY = delegate (float _f) { return Mathf.RoundToInt(_f).ToString(); };
        }
        float graphHeight = graphContainer.sizeDelta.y;
        float graphWidth = graphContainer.sizeDelta.x;




        float yMinimum = valueList[0];
        float yMaximum = valueList[0];
        foreach (int value in valueList)
        {
            if (value > yMaximum)
            {
                yMaximum = value;
            }
            if (value < yMinimum)
            {
                yMinimum = value;
            }
        }
        yMaximum = yMaximum + ((yMaximum-yMinimum)*0.2f);
        yMinimum = yMinimum - ((yMaximum-yMinimum)* 0.2f);

        float xSize = graphWidth/15;

        GameObject lastCircleGameObject = null;
        for (int i = 0; i<valueList.Count; i++)
        {
            float xPosition = xSize+ i * xSize;
            float yPosition = ((valueList[i]-yMinimum) / (yMaximum-yMinimum)) * graphHeight;
            GameObject circleGameObject =  CreateCircle((new Vector2(xPosition, yPosition)));
            if(lastCircleGameObject != null)
            {
                CreateDotConnection(lastCircleGameObject.GetComponent<RectTransform>().anchoredPosition, circleGameObject.GetComponent<RectTransform>().anchoredPosition);
            }
            lastCircleGameObject = circleGameObject;

            RectTransform labelX = Instantiate(labelTemplateX);
            labelX.SetParent(graphContainer,false);
            labelX.gameObject.SetActive(true);
            labelX.anchoredPosition = new Vector2(xPosition, -7f);
            labelX.GetComponent<Text>().text = getAxisLabelX(i);


            RectTransform dashX = Instantiate(dashTemplateY);
            dashX.SetParent(graphContainer, false);
            dashX.gameObject.SetActive(true);
            dashX.anchoredPosition = new Vector2(xPosition, -7f);

        }

y方向のラベルを何個表示するか

 int separatorCount = 10;
        for (int i = 0; i <= separatorCount; i++)
        {
            RectTransform labelY = Instantiate(labelTemplateY);
            labelY.SetParent(graphContainer, false);
            labelY.gameObject.SetActive(true);
            float normalizedValue = i * 1f / separatorCount;
            labelY.anchoredPosition = new Vector2(-7f, normalizedValue * graphHeight);
            labelY.GetComponent<Text>().text = getAxisLabelY(yMinimum+( normalizedValue * (yMaximum-yMinimum)));



            RectTransform dashY = Instantiate(dashTemplateX);
            dashY.SetParent(graphContainer, false);
            dashY.gameObject.SetActive(true);
            dashY.anchoredPosition = new Vector2(-7f, normalizedValue * graphHeight);

        }



ここで点を結びます UtilsClass.GetAngleFromVector(dir)を使うので、インポートしなければいけません。 これは点と点との角度を表しているものだと思っています。

    private void CreateDotConnection(Vector2 dotPositionA, Vector2 dotPostionB)
    {
        GameObject gameObject = new GameObject("dotConnection", typeof(Image));
        gameObject.transform.SetParent(graphContainer, false);
        gameObject.GetComponent<Image>().color = new Color(0, 1, 0, 1f);

        RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
        Vector2 dir = (dotPostionB - dotPositionA).normalized;
        float distance = Vector2.Distance(dotPositionA, dotPostionB);
        
        rectTransform.anchorMin = new Vector2(0, 0);
        rectTransform.anchorMax = new Vector2(0, 0);
        rectTransform.sizeDelta = new Vector2(distance, 3f);
        rectTransform.anchoredPosition = dotPositionA +dir*distance*0.5f ;
        rectTransform.localEulerAngles = new Vector3(0, 0, UtilsClass.GetAngleFromVector(dir));
    }