エンジニア

なんくるないさ

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

Unityでグラフを書く(棒グラフ偏)

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

jump1268.hatenablog.com

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

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


Unity Tutorial - Create a Graph

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

f:id:jump1268:20190812144954p:plain

f:id:jump1268:20190812144956p:plain

f:id:jump1268:20190817220458p:plain

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

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


全コード

public class Window_Bar : MonoBehaviour
{
    [SerializeField] private Sprite dotSprite;
    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;

    public Text your_face_text;
    public Text std_text;
    public Text rank_text;
    public Text mean_text;
    public Text max_text;
    public Text min_text;

    public List<GameObject> gameObjectList = new List<GameObject>();
    List<int> valueList = new List<int>();

    public bool data_recieved = true;
    public bool your_position_bool = false;
    public int your_position = 4;

    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 n = 0;
        while (PlayerPrefs.GetFloat("Face" + n, 0) > 0.1)
        {
            n = n + 1;
        }
        n = n - 1;
        float your_face = PlayerPrefs.GetFloat("Face" + n, 0);
        your_face_text.text = "あなたの大きさ"+"\n" + "\n" + your_face.ToString();



        NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject>("EveryMean");
        query.FindAsync((List<NCMBObject> objList, NCMBException e) =>
        {
            if (e != null)
            {
                std_text.text =  "error";
                Debug.Log("mean error");
            }
            else
            {
                var std = float.Parse(objList[0]["EveryStd"].ToString());
                var mean = float.Parse(objList[0]["EveryMean"].ToString());
                var you_face = PlayerPrefs.GetFloat("Face" + n, 0);
                var hensati =-(you_face - mean) * 10 / std + 50;
                std_text.text =  "偏差値" + "\n" + "\n" + hensati ;
                mean_text.text = "全体平均" +"\n"+"\n" + mean ;
                Debug.Log("meanget");
            }
        });
        

        //No_Loginからデータをとってくる
        query = new NCMBQuery<NCMBObject>("No_Login");
        var a = 0;
        query.Limit = 1000;
        query.OrderByAscending("Face_s");
        var your_rank = false;
        rank_text.text = "読み込み中です";
        query.FindAsync((List<NCMBObject> objList, NCMBException e) =>
        {
            if (e != null)
            {
                rank_text.text = "インターネットにつないでください";
            }
            else
            {
                
                for (int t = 0; t < 11; t++)
                {
                    
                    a = 0;
                    var kei = 0;
                    for (int k = 0; k< valueList.Count; k++)
                    {
                        kei += valueList[k];
                    }
                    //ここにfor文などで範囲を指定してvalueListに追加するのがよいのか?
                    for (int i = kei; i < objList.Count; i++)
                    {
                        var data = float.Parse(objList[i]["Face_s"].ToString());

                        if (data > 300 + (t * 8) && data < 308 + (t * 8))
                        {
                            a = a + 1;
                        }
                        //順位を出す
                        if (your_rank==false && Mathf.Floor(data*100) == Mathf.Floor(your_face*100))
                        {
                            your_rank = true;
                            rank_text.text = "順位" + "\n" + "\n" + i + "位" + "/" + objList.Count + "人中";
                        }
                        //赤く光らせる場所を出す
                        if (your_position_bool == false && your_face > 300 + (t * 8) && your_face < 308 + (t * 8))
                        {
                            your_rank = true;
                            your_position_bool = true;
                            your_position = t;
                            Debug.Log(t);
                        }
                    }
                   
                    valueList.Add(a);
                }
            }
        });
   

        print("queryAfter");

        var gameObjectList = new List<GameObject>();


        //ShowGraph(valueList, (int _i) => "Day" + (_i + 1), (float _f) => Mathf.RoundToInt(_f)+"cm^2") ;
        
    }

    private GameObject CreateDot(Vector2 anchoredPosition)
    {
        GameObject gameObject = new GameObject("dot", typeof(Image));
        gameObject.transform.SetParent(graphContainer, false);
        gameObject.GetComponent<Image>().sprite = dotSprite;
        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<int> 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;
            }
        }


        float xSize = graphWidth/7;

        for (int i = 0; i<valueList.Count; i++)
        {
            float xPosition = xSize+ i * xSize;
            float yPosition = ((valueList[i]-yMinimum) / (yMaximum-yMinimum)) * graphHeight;

            GameObject barGameObject;
            if (i == your_position)
            {
                print("ok");
                barGameObject = CreateBar(new Vector2(xPosition, yPosition), xSize * 0.9f,true);
            }
            else
            {
                barGameObject = CreateBar(new Vector2(xPosition, yPosition), xSize * 0.9f,false);
            }
            gameObjectList.Add(barGameObject);

            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));
    }


    private GameObject CreateBar(Vector2 graphPosition,float barWidth,bool position)
    {
        GameObject gameObject = new GameObject("bar", typeof(Image));
        gameObject.transform.SetParent(graphContainer, false);
        if (position == true)
        {
            gameObject.GetComponent<Image>().color = Color.red;
        }
        else
        {
            gameObject.GetComponent<Image>().color = Color.green;
        }
        RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
        rectTransform.anchoredPosition = new Vector2(graphPosition.x, 0f);
        rectTransform.sizeDelta = new Vector2(barWidth,graphPosition.y);
        rectTransform.anchorMin = new Vector2(0, 0);
        rectTransform.anchorMax = new Vector2(0, 0);
        rectTransform.pivot = new Vector2(0.5f, 0f);
        return gameObject;
    }


    // Start is called before the first frame update
    void Start()
    {
        var num_of_all = PlayerPrefs.GetString("num_of_all", "error1");
        var min_of_all = PlayerPrefs.GetString("min_of_all", "err2");
        var max_of_all = PlayerPrefs.GetString("max_of_all", "err3");
        max_text.text = "最大記録"+"\n" + "\n" + max_of_all;
        min_text.text = "最小記録" + "\n" + "\n" + min_of_all;

    }


    // Update is called once per frame
    void Update()
    {
        if (data_recieved == true && valueList.Count == 11 )
        {
            ShowGraph(valueList, (int _i) => (_i) * 8 + 300 + "~"+ ((_i+1)*8+300), (float _f) => Mathf.RoundToInt(_f) + "人");
            data_recieved = false;
        }
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            start = Input.mousePosition;
        }
        if (Input.GetKeyUp(KeyCode.Mouse0))
        {
            end = Input.mousePosition;
            deltaX = (start - end).x;
        }
        this.gameObject.transform.Translate(-deltaX * 1f * Time.deltaTime, 0f, 0f);
        deltaX *= 0.9f;
    }
}

解説

まず棒グラフとは関係ないのですが、偏差値を計算している箇所です。 標準偏差と平均をサーバからとってきて計算します

        NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject>("EveryMean");
        query.FindAsync((List<NCMBObject> objList, NCMBException e) =>
        {
            if (e != null)
            {
                Meantext.text =  "error";
                Debug.Log("mean error");
            }
            else
            {
                var std = float.Parse(objList[0]["EveryStd"].ToString());
                var mean = float.Parse(objList[0]["EveryMean"].ToString());
                var you_face = PlayerPrefs.GetFloat("Face" + n, 0);
                var hensati =-(you_face - mean) * 10 / std + 50;
                Meantext.text = "全体平均:" + mean + "" + "\n" + "あなたの偏差値:" + hensati + "" + "\n";
                Debug.Log("meanget");
            }
        });

棒グラフの一つづつの範囲に何個値が入っているかをリストに追加する場所です query.countを最初使っていたのですが、それだとリクエストが増えるので、 一回全レコードを取得して、それを数えています。 また今の自分の順位と、その大きさがどの範囲に含まれるかを計算しています

       //No_Loginからデータをとってくる
        query = new NCMBQuery<NCMBObject>("No_Login");
        var a = 0;
        query.Limit = 100;
        query.OrderByAscending("Face_s");
        query.FindAsync((List<NCMBObject> objList, NCMBException e) =>
        {
            if (e != null)
            {
                Infromation_NCMb.text = "インターネットにつないでください";
            }
            else
        {   
                    a = 0;
                    var kei = 0;
                    for (int k = 0; k< valueList.Count; k++)
                    {
                        kei += valueList[k];
                    }
                    for (int i = kei; i < objList.Count; i++)
                    {
                        var data = float.Parse(objList[i]["Face_s"].ToString());

                        if (data > 300 + (t * 8) && data < 308 + (t * 8))
                        {
                            a = a + 1;
                        }
                        //順位を出す
                        if (your_rank==false && Mathf.Floor(data*100) == Mathf.Floor(your_face*100))
                        {
                            your_rank = true;
                            rank_text.text = "順位" + "\n" + "\n" + i + "位" + "/" + objList.Count + "人中";
                        }
                        //赤く光らせる場所を出す
                        if (your_position_bool == false && your_face > 300 + (t * 8) && your_face < 308 + (t * 8))
                        {
                            your_rank = true;
                            your_position_bool = true;
                            your_position = t;
                            Debug.Log(t);
                        }
                    }
                    valueList.Add(a);
                }
            }
        });

上で計算したyour_positionとiが一緒の時は棒グラフの色を変えるために、引数を変えています。 trueのときだけ色を変えます

            if (i == your_position)
            {
                print("ok");
                barGameObject = CreateBar(new Vector2(xPosition, yPosition), xSize * 0.9f,true);
            }
            else
            {
                barGameObject = CreateBar(new Vector2(xPosition, yPosition), xSize * 0.9f,false);
            }

上でしたようにbool値を引数で受け取り処理します。

    private GameObject CreateBar(Vector2 graphPosition,float barWidth,bool position)
    {
        GameObject gameObject = new GameObject("bar", typeof(Image));
        gameObject.transform.SetParent(graphContainer, false);
        if (position == true)
        {
            gameObject.GetComponent<Image>().color = Color.red;
        }
        else
        {
            gameObject.GetComponent<Image>().color = Color.green;
        }
        RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
        rectTransform.anchoredPosition = new Vector2(graphPosition.x, 0f);
        rectTransform.sizeDelta = new Vector2(barWidth,graphPosition.y);
        rectTransform.anchorMin = new Vector2(0, 0);
        rectTransform.anchorMax = new Vector2(0, 0);
        rectTransform.pivot = new Vector2(0.5f, 0f);
        return gameObject;
    }

棒グラフの一つづつがそろうのを待ちます。 一度描画したら二度とshowGraphしないようにif文でしています

    // Start is called before the first frame update
    void Start()
    {
        var num_of_all = PlayerPrefs.GetString("num_of_all", "error1");
        var min_of_all = PlayerPrefs.GetString("min_of_all", "err2");
        var max_of_all = PlayerPrefs.GetString("max_of_all", "err3");
        max_text.text = "最大記録"+"\n" + "\n" + max_of_all;
        min_text.text = "最小記録" + "\n" + "\n" + min_of_all;

    }


    // Update is called once per frame
    void Update()
    {
        if (data_recieved == true && valueList.Count == 11 )
        {
            ShowGraph(valueList, (int _i) => (_i) * 8 + 300 + "~"+ ((_i+1)*8+300), (float _f) => Mathf.RoundToInt(_f) + "人");
            data_recieved = false;
        }
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            start = Input.mousePosition;
        }
        if (Input.GetKeyUp(KeyCode.Mouse0))
        {
            end = Input.mousePosition;
            deltaX = (start - end).x;
        }
        this.gameObject.transform.Translate(-deltaX * 1f * Time.deltaTime, 0f, 0f);
        deltaX *= 0.9f;
    }
}