【ナナオン】マルチプレイでメンバーが揃っていてもライブが始まらない問題。

ガチのバンドリーマーがナナオンをプレイしてみて思ったことその1.

マルチプレイの問題なんですけど、

メンバーが揃っていてもライブが始まらない問題。

これは推測なのですが、

ナナオンのマルチプレイって、野良でもライブ後に

「もう一度ルームに入りますか?」

って聞かれるんですよ。

ここできちんとルームに入るなり、退室するなりすれば良いんですが、

おそらくここで放置している人がいるみたいなんです。

そうなるとどうなるかというと、

そのルームは再入室待ち状態になって、ルーム自体が地雷(いつまでたってもライブが始まらないルーム)になってしまっていると思うんです。

それが影響してか、通常のマルチプレイでもマッチング率が低い。

これは・・・これでは・・・周回が捗らない・・・

何か対策を打ちませんと、過疎ってしまいますぞ・・・?

そういえば、ガルパリリース当初は協力ライブを放置してイベントポイントを稼ぐっていう不届き者がいたけど(その後のアップデートでライブ参加の実績が無ければイベントポイントが獲得できないように修正された)、ナナオンは何か対策取っているのかな?

【LARAVEL】【ダイエット支援】VPSにデプロイする

前回までの状況はこちら。

最低限必要な機能は出来上がったので、本番サーバであるVPSで稼働させます。

gitからソースファイルをクローン

git clone https://github.com/takishita2nd/diet-mng.git

パーミッション変更

chmod -R 777 storage/
chmod 777 bootstrap/cache/

.envを作成し、データベースの設定と、URLの設定を記入。

cp .env.example .env
vi .env

データベースにログインし、データベースを作成。

mysql> create database diet_mng;

composerを使ってLaravelの環境を構築

sudo apt-get install composer
composer install

.envにkyeを生成

php artisan key:generate

npmでVue.jsを使用できるようにする

sudo apt-get install npm
sudo apt-get install libpng-dev
npm install
npm run prod

データベース構築(マイグレート)

php artisan migrate

nginxの設定

URLでブログとダイエット管理を分けようと思ったのですが、上手く設定できなかったので、ポート番号で分けます。

cd /etc/nginx/sites-enabled
sudo cp default laravel
sudo vi laravel
server {
        listen 8443 ssl default_server;
        listen [::]:8443 ssl default_server;
        ssl_certificate     /etc/letsencrypt/live/taki-lab.site/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/taki-lab.site/privkey.pem;

        root /var/www/html/diet-mng/public;

        index index.php index.html index.htm index.nginx-debian.html;

        server_name taki-lab.site;

        location / {
                try_files $uri $uri/ /index.php?$query_string;
        }

        location ~ \.php$ {
                fastcgi_pass   unix:/run/php/php7.2-fpm.sock;
                fastcgi_index  index.php;
                fastcgi_param  SCRIPT_FILENAME  $document_root/index.php;
                include        fastcgi_params;
        }
}

ポート番号は8443を使用しました。

ブログ用の設定では動きませんので、いろんなサイトを調べた結果、このような設定で動作できます。

rootはプロジェクトディレクトリ/publicを指定します。publicの下のindex.phpにアクセスするように指定します。

nginxの設定と読み込み

sudo systemctl reload nginx.service

何も表示されなければ書式は合っています。

エラーが出たら/var/log/nginx/error.logを確認してください。

ここまで上手くいけばトップページが表示されるはず。

トップページ書き換えるの忘れてた。

ログインしてデータ入力できることを確認する。

以下、詰まったところ

modelに以下を記入しないとデータベースクエリが動かなかった。

protected $table = 'weight_managements';

https://qiita.com/igz0/items/d14fdff610dccadb169e

テーブル名を明示的に指定しないといけないらしい。

ここらへん、ローカル環境にフィードバックさせます。

というわけで、

ダイエット管理サイト、以下からアクセスできますので、よかったら使ってみてください。

https://taki-lab.site:8443/

【北海道大戦】マップデータを描画する

前回までの状況はこちら。

gitHubにソースファイルを公開しました。

https://github.com/takishita2nd/HokkaidoWar

前回作成したJsonファイルを読み込んで、画面に出力させたいと思います。

まずはJsonデータをオブジェクトで持つためのクラスを作成します。

    [JsonObject("MapDataModel")]
    public class MapData
    {
        [JsonProperty("list")]
        public List[] list { get; set; }
    }

    [JsonObject("List")]
    public class List
    {
        [JsonProperty("name")]
        public string name { get; set; }
        [JsonProperty("point")]
        public Point[] point { get; set; }
    }

    [JsonObject("Point")]
    public class Point
    {
        [JsonProperty("x")]
        public int x { get; set; }
        [JsonProperty("y")]
        public int y { get; set; }
    }

これは、Jsonをクリップボードにある状態で、「編集」→「形式を選択して貼り付け」→「Json」を選択すると簡単にJsonに合わせてクラスを作成してくれます。

では、実際にJsonを読み込んでオブジェクト化する処理。

    class FileAccess
    {
        private const string _filename = "hokkaido.json";
        public static MapData Load()
        {
            string str = string.Empty;
            using (var stream = new StreamReader(_filename, true))
            {
                str = stream.ReadToEnd();
            }
            return JsonConvert.DeserializeObject<MapData>(str);
        }
    }

これをAltseedで画面に表示させます。

    class HokkaidoWar
    {
        MapData mapData = null;
        public HokkaidoWar()
        {
            mapData = FileAccess.Load();
        }

        public void Run()
        {
            asd.Engine.Initialize("北海道大戦", 1800, 1000, new asd.EngineOption());

            // 下地
            var background = new asd.GeometryObject2D();
            asd.Engine.AddObject2D(background);
            var bgRect = new asd.RectangleShape();
            bgRect.DrawingArea = new asd.RectF(0, 0, 1800, 1000);
            background.Shape = bgRect;

            var r = new Random();
            foreach (var map in mapData.list)
            {
                var color = new asd.Color((byte)r.Next(0, 255), (byte)r.Next(0, 255), (byte)r.Next(0, 255));
                foreach (var point in map.point)
                {
                    var geometryObj = new asd.GeometryObject2D();
                    geometryObj.Color = color;
                    asd.Engine.AddObject2D(geometryObj);
                    var rect = new asd.RectangleShape();
                    rect.DrawingArea = new asd.RectF(24 * point.x + 50, 24 * point.y + 50, 24, 24);
                    geometryObj.Shape = rect;
                }
            }

            while (asd.Engine.DoEvents())
            {
                asd.Engine.Update();
            }
            asd.Engine.Terminate();
        }
    }
        static void Main(string[] args)
        {
            HokkaidoWar hokkaidoWar = new HokkaidoWar();
            hokkaidoWar.Run();
        }

色はとりあえず乱数で振ることにしました。

実行結果はこうなりました。

【ラズパイ】【GLCD】画面に時刻を表示する

前回までの状況はこちら。

最新ソースをgitHubにアップしました。

https://github.com/takishita2nd/GLCD

ここまで来たら、時刻を表示するくらい簡単にできると思うんよ。

def __main():
    PinsInit(20, 7, 8, 9, 18, 19, 10, 11, 12, 13, 14, 15, 16, 17)
    GLCDInit()
    GLCDDisplayClear()
    GLCDBox(0, 0, 127, 62)
    GLCDLine(0, 15, 127, 15)

    try:
        while True:
            GLCDPuts(30, 38, datetime.datetime.now().strftime('%H:%M:%S'))
            time.sleep(0.1)
    except KeyboardInterrupt:
        GLCDDisplayClear()
        GPIO.cleanup()

うん、これでラズパイで時計が作れる。

【ナナオン】ガチのバンドリーマーがナナオンをプレイして見た。

22/7(ナナブンノニジュウニ)音楽の時間。

オイラはリズムゲームを本格的にプレイ始めたのはガルパが初めてだったので、他のリズムゲームもプレイして見たいとも思っていまして、

で、何故このゲームかというと、最近リリースされたばっかりなので、他のプレイヤーとそんなに差が開いていないだろう、と思ったためで、

まぁ修行っす。修行。

で、このゲーム、一部の界隈ではガルパのパクリと言われているゲームです(言っちまった。)

まぁ、ゲームシステムが似ているゲームは他にもたくさん知っているのでいちいち驚きはしません。

ゲームは22/7のメンバーが(秋元先生の)指令で学校に転校になって、主人公は彼女達の担任の先生兼マネージャーとなります。

まぁ、あれこれ差分を洗い出すのはめんどくさいので、とりあえずプレイして見るよ。

やはりエキスパート所見フルコンボは無理でした。

ハードならなんとか。

ライブ設定ですが、ノードのスピードはガルパと同じ10.5、スキンはノードの区別がわかりやすい(と思った)タイプ2を選択。

コンボ数の表示は中央だとめちゃくちゃ邪魔なので、右に表示。

スキルのカットインも非表示。

あと、マルチプレイの特殊演出も消したいんだけど、設定ある?

やってみた感想としては、やっぱり譜面のクセというか、ガルパと違っててちょっとやりづらいというのも最初はありましたが、慣れればなんとかなるかな、と思いました。

そもそも、ガルパは7レーンですが、ナナオンは5レーンなので。

でもリズムゲームとしては悪くないと思います。

今はガルパの方がイベント終了後のインターバル期間なので、もう少しやってみます。

あと、気になったのが、さすがアニプレックスというか、

カバー曲にコネクト(ClariS)、ideal white(綾野ましろ)、IGNITE(藍井エイル)、outh sign(LiSA)、桃色タイフーン(春奈るな)。

お気づきでしょうか。

みんなソニーミュージックの面々だということを。

あーこれらはガルパではカバーされないな(確信)。

【ラズパイ】【GLCD】画面に直線を引く

前回までの状況はこちら。

最新ソースをgitHubにアップしました。

https://github.com/takishita2nd/GLCD

今回は画面に直線を引きます。

始点と終点の座標を与えると、直線を引くようにします。

傾きがあった場合でも引けるようにします。

といっても、サンプルをPythonに書き換えただけですが。

def GLCDLine(Xp0, Yp0, Xp1, Yp1):
    #差分の大きい方を求める
    steep = (abs(Yp1 - Yp0) > abs(Xp1 - Xp0))
    #X,Yの入れ替え
    if steep == True:
        x = Xp0
        Xp0 = Yp0
        Yp0 = x
        x = Xp1
        Xp1 = Yp1
        Yp1 = x
    if Xp0 > Xp1:
        x = Xp0
        Xp0 = Xp1
        Xp1 = x
        x = Yp0
        Yp0 = Yp1
        Yp1 = x
    #傾き計算
    deltax = Xp1 - Xp0
    deltay = abs(Yp1 - Yp0)
    er = 0
    y = Yp0
    ystep = 0
    #傾きでステップの正負を切り替え
    if Yp0 < Yp1:
        ystep = 1
    else:
        ystep = -1
    #直線を点で描画
    for x in range(Xp0, Xp1 + 1):
        if steep == True:
            GLCDPutPixel(y, x)
        else:
            GLCDPutPixel(x, y)
        er += deltay
        if (er << 1) >= deltax:
            y += ystep
            er -= deltax

まずどちらの方向に描画していくか(縦or横)を判定します。

基本的に絶対値が大きい方向に描画していきます。

縦方向に描画していく場合はx/yを入れ替えます。

あとは傾きに応じてy/xを加算しながらx/y方向に描画していきます。

def __main():
    PinsInit(20, 7, 8, 9, 18, 19, 10, 11, 12, 13, 14, 15, 16, 17)
    GLCDInit()
    GLCDDisplayClear()
    GLCDBox(0, 0, 127, 63)
    GLCDLine(0, 15, 127, 15)

    try:
        while True:
            time.sleep(1.0)
    except KeyboardInterrupt:
        GLCDDisplayClear()
        GPIO.cleanup()
def __main():
    PinsInit(20, 7, 8, 9, 18, 19, 10, 11, 12, 13, 14, 15, 16, 17)
    GLCDInit()
    GLCDDisplayClear()
    GLCDBox(0, 0, 127, 63)
    GLCDLine(0, 15, 127, 18)

    try:
        while True:
            time.sleep(1.0)
    except KeyboardInterrupt:
        GLCDDisplayClear()
        GPIO.cleanup()

【ラズパイ】【GLCD】画面に四角の線を引く※追記あり

前回までの状況はこちら。

最新ソースをgitHubにアップしました。

https://github.com/takishita2nd/GLCD

画面に四角を書きます。

これもこちらのAudiunoのサンプルコードをPython用に書き換えた物です。

def GLCDBox(Xp0, Yp0, Xp1, Yp1):
    for i in range(Xp0, Xp1 + 1):
        GLCDPutPixel(i, Yp0)
        GLCDPutPixel(i, Yp1)
    for i in range(Yp0 + 1, Yp1):
        GLCDPutPixel(Xp0, i)
        GLCDPutPixel(Xp1, i)

def GLCDPutPixel(Xp, Yp):
    #ラインの選択処理
    L = 1 << (Yp % 8)
    #LCDに表示するアドレスの位置をセットする
    SetLocation(Xp, Yp)
    #LCD画面の現在表示内容に指定位置のビット(L)をON(XOR)させ、そのデータをLCDに送る
    L = ReadData() | L
    SetAddress(SetCol)
    WriteData(L)

def ReadData():
    #データピンを入力に設定
    for i in range(8):
        GPIO.setup(DATA_p[i], GPIO.IN)
    #読み込みモードにする
    GPIO.output(RW_p, GPIO.HIGH)
    GPIO.output(RS_p, GPIO.HIGH)
    #データを読み込む
    GPIO.output(E_p, GPIO.HIGH)
    GPIO.output(E_p, GPIO.LOW)
    ans = 0
    GPIO.output(E_p, GPIO.HIGH)
    for i in range(8):
        ans = ans | (GPIO.input(DATA_p[i]) << i)
    GPIO.output(E_p, GPIO.LOW)
    #書き込みモードにする
    GPIO.output(RW_p, GPIO.LOW)
    #データピンを出力に設定
    for i in range(8):
        GPIO.setup(DATA_p[i], GPIO.OUT)
    return ans

ピクセルを置く際には、一度表示させているデータを取得し、そのデータのORをとって表示させます。

※サンプルではここがXORになっていたけど、どっちが正しいんだろ?

READする時は、ピンのモードを出力から入力に切り替える必要があります。

実行結果はこうなりました。

んんー?なんかおかしいぞ?

もしかして、信号の読み取りでおかしなデータを読み込んでいるんじゃ無いか?

タイミングチャートを確認する。

ここを確認すると、E信号のHIGHにしてからLOWにするまでの時間(tWH、tWL)が最低450nsと書かれています。

なので、この間に0.5ms(500ns)をsleepを入れてみます。※追記あり

EWAIT = 0.0005
def ReadData():
    #データピンを入力に設定
    for i in range(8):
        GPIO.setup(DATA_p[i], GPIO.IN)
    #読み込みモードにする
    GPIO.output(RW_p, GPIO.HIGH)
    GPIO.output(RS_p, GPIO.HIGH)
    #データを読み込む
    GPIO.output(E_p, GPIO.HIGH)
    time.sleep(EWAIT) #★
    GPIO.output(E_p, GPIO.LOW)
    ans = 0
    GPIO.output(E_p, GPIO.HIGH)
    time.sleep(EWAIT) #★
    for i in range(8):
        ans = ans | (GPIO.input(DATA_p[i]) << i)
    GPIO.output(E_p, GPIO.LOW)
    #書き込みモードにする
    GPIO.output(RW_p, GPIO.LOW)
    #データピンを出力に設定
    for i in range(8):
        GPIO.setup(DATA_p[i], GPIO.OUT)
    return ans

def command(value, mode):
    GPIO.output(RS_p, mode)
    for i in range(8):
        GPIO.output(DATA_p[i], (value >> i) & 0x01)
    GPIO.output(E_p, GPIO.HIGH)
    time.sleep(EWAIT) #★
    GPIO.output(E_p, GPIO.LOW)

モッサリしてる。

もう一度タイミングチャートを確認。

tNはEをHIGHにしてからデータ端子0-7が出力されるまでの時間です。

この表を見れば最大25nsと書いてあります。

なので、E信号のsleep時間を0.03ms(30ns)まで縮めてみましょう。※追記あり

EWAIT = 0.00003

だいぶマシになったのではないでしょうか

動作も安定してオールOKです。

いやーすごい濃いことやってるなー

※追記

ああああ!

単位間違えてるじゃん!

これじゃあμsじゃん!

正しくnsに修正。

EWAIT = 0.0000005

【C#】【ピクロス】【ALTSEED】解析パターン11

前回までの状況はこちら。

最新ソースはこちら。(gitHub)

https://github.com/takishita2nd/Picross

次はこちらの問題を解いてみます。

解析実行。

この状態で止まってしまいましたね。

また新しい解析パターンを追加する必要があるようです。

ここに注目してみました。

この列では4が一番大きい数字で、すでに4マス塗られていますので、この4はすでに確定します。

なので、この4は解析済みにし、両端をマスクする、というロジックが必要です。

これを実装します。

考え方は、

  1. 行、列から連続する塗られたマスを取得する
  2. その中から最大の物を取得する
  3. 数字の中で一番大きい物を取得する
  4. 取得した連続するマスの数が、一番多き数字と一致するかどうかを確認する
  5. 一致するならば、その数字を解析済みにし、塗られた連続するマスの両端をマスクする

コードはこうなりました。

        // 解析パターンその11
        // すでに塗っている場所を解析済みにする
        private void pattern11()
        {
            // Row
            pattern11Row();
            // Col
            pattern11Col();
        }

        private void pattern11Row()
        {
            int row = 0;
            foreach (var rowlist in rowNumbers)
            {
                if (rowlist.IsAnalyzed())
                {
                    row++;
                    continue;
                }
                rowlist.AnalyzeDatas.Reverse();

                // 塗られている連続したマスを数える
                List<List<BitmapData>> data = new List<List<BitmapData>>();
                {
                    List<BitmapData> dataList = new List<BitmapData>();
                    for (int col = 0; col < colNumbers.Count; col++)
                    {
                        if (_bitmapData[row, col].IsPainted() == true)
                        {
                            dataList.Add(_bitmapData[row, col]);
                        }
                        else
                        {
                            if (dataList.Count != 0)
                            {
                                data.Add(dataList);
                                dataList = new List<BitmapData>();
                            }
                        }
                    }
                    if (dataList.Count != 0)
                    {
                        data.Add(dataList);
                    }

                    if (data.Count == 0)
                    {
                        row++;
                        rowlist.AnalyzeDatas.Reverse();
                        continue;
                    }
                }

                List<BitmapData> targetDataList = null;
                // 最大の物を取得する
                foreach (var datalist in data)
                {
                    if(targetDataList == null)
                    {
                        targetDataList = datalist;
                    }
                    else if(targetDataList.Count < datalist.Count)
                    {
                        targetDataList = datalist;
                    }
                }

                AnalyzeData targetRowList = null;
                foreach(var rowdata in rowlist.AnalyzeDatas)
                {
                    if(targetRowList == null)
                    {
                        targetRowList = rowdata;
                    }
                    else if(targetRowList.Value < rowdata.Value)
                    {
                        targetRowList = rowdata;
                    }

                }

                if(targetDataList.Count == targetRowList.Value)
                {
                    //解析済みにする
                    targetRowList.Analyzed();
                    if(targetDataList[0].Col > 0)
                    {
                        if (_bitmapData[targetDataList[0].Row, targetDataList[0].Col - 1].IsValid() == false)
                        {
                            _bitmapData[targetDataList[0].Row, targetDataList[0].Col - 1].Mask();
                        }
                    }
                    if (targetDataList[targetDataList.Count - 1].Col < colNumbers.Count - 1)
                    {
                        if (_bitmapData[targetDataList[targetDataList.Count - 1].Row, targetDataList[targetDataList.Count - 1].Col + 1].IsValid() == false)
                        {
                            _bitmapData[targetDataList[targetDataList.Count - 1].Row, targetDataList[targetDataList.Count - 1].Col + 1].Mask();
                        }
                    }
                }

                rowlist.AnalyzeDatas.Reverse();
                row++;
            }
        }

        private void pattern11Col()
        {
            int col = 0;
            foreach (var collist in colNumbers)
            {
                if (collist.IsAnalyzed())
                {
                    col++;
                    continue;
                }
                collist.AnalyzeDatas.Reverse();

                // 塗られている連続したマスを数える
                List<List<BitmapData>> data = new List<List<BitmapData>>();
                {
                    List<BitmapData> dataList = new List<BitmapData>();
                    for (int row = 0; row < rowNumbers.Count; row++)
                    {
                        if (_bitmapData[row, col].IsPainted() == true)
                        {
                            dataList.Add(_bitmapData[row, col]);
                        }
                        else
                        {
                            if (dataList.Count != 0)
                            {
                                data.Add(dataList);
                                dataList = new List<BitmapData>();
                            }
                        }
                    }
                    if (dataList.Count != 0)
                    {
                        data.Add(dataList);
                    }

                    if (data.Count == 0)
                    {
                        col++;
                        collist.AnalyzeDatas.Reverse();
                        continue;
                    }
                }

                List<BitmapData> targetDataList = null;
                // 最大の物を取得する
                foreach (var datalist in data)
                {
                    if (targetDataList == null)
                    {
                        targetDataList = datalist;
                    }
                    else if (targetDataList.Count < datalist.Count)
                    {
                        targetDataList = datalist;
                    }
                }

                AnalyzeData targetColList = null;
                foreach (var coldata in collist.AnalyzeDatas)
                {
                    if (targetColList == null)
                    {
                        targetColList = coldata;
                    }
                    else if (targetColList.Value < coldata.Value)
                    {
                        targetColList = coldata;
                    }

                }

                if (targetDataList.Count == targetColList.Value)
                {
                    //解析済みにする
                    targetColList.Analyzed();
                    if (targetDataList[0].Row > 0)
                    {
                        if (_bitmapData[targetDataList[0].Row - 1, targetDataList[0].Col].IsValid() == false)
                        {
                            _bitmapData[targetDataList[0].Row - 1, targetDataList[0].Col].Mask();
                        }
                    }
                    if (targetDataList[targetDataList.Count - 1].Row < colNumbers.Count - 1)
                    {
                        if (_bitmapData[targetDataList[targetDataList.Count - 1].Row + 1, targetDataList[targetDataList.Count - 1].Col].IsValid() == false)
                        {
                            _bitmapData[targetDataList[targetDataList.Count - 1].Row + 1, targetDataList[targetDataList.Count - 1].Col].Mask();
                        }
                    }
                }

                collist.AnalyzeDatas.Reverse();
                col++;
            }
        }

実行結果はこうなりました。

まだ完全では無いですが、少し進みましたね。

【LARAVEL】【ダイエット支援】グラフ表示用のデータを取得するその2

前回までの状況はこちら。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/diet-mng

前回はAPI側を作成したので、今回はフロント側を作成します。

使用したライブラリは、chart.jsです。

なんか、Vue.js用に拡張されたvue-chart.jsというものもあるみたいなのですが、うまく動かなかったので、chart.jsにしました。

原因はわからん。

npm install chart.js --save

でインストール。

resouces/assets/app.jsに以下を書き込みます。

require('chart.js');

これでどこでもchart.jsが使えます。

<template>
    <div>
        <div class="dashboard">
            <div class="chart">
                <canvas id="weight"></canvas>
            </div>
            <div class="command">
                <ul>
                    <li><a @click="onClickInput">クイック入力</a></li>
                    <li><a href="/weight">詳細</a></li>
                </ul>
            </div>
        </div>
        <weight-input-dialog-component :show="showInputDialogContent" @update="invokeUpdateList"></weight-input-dialog-component>
    </div>
</template>

<script>
export default {
    data() {
        return {
            showInputDialogContent: false,
            datetimeList: [],
            weightList: [],
            fat_rateList: [],
            bmiList: [],
        };
    },
    created: function() {
    },
    mounted: function() {
        this.graphUpdate();
    },
    methods: {
        onClickInput: function() {
            this.showInputDialogContent = true;
        },
        invokeUpdateList: function() {
            this.graphUpdate();
        },
        graphUpdate: function() {
            this.datetimeList = [];
            this.weightList = [];
            this.fat_rateList = [];
            this.bmiList = [];
            var ctx = document.getElementById("weight");
            var self = this;
            axios.post('api/weight/graph').then(function(response){
                response.data.datas.forEach(element => {
                    self.datetimeList.push(element.datetime);
                    self.weightList.push(element.weight);
                    self.fat_rateList.push(element.fat_rate);
                    self.bmiList.push(element.bmi);
                });
                var myChart = new Chart(ctx, {
                    type: 'line',
                    data: {
                        labels: self.datetimeList,
                        datasets: [
                            {
                                yAxisID: 'weight',
                                label: '体重(kg)',
                                data: self.weightList,
                                borderColor: "rgba(255,0,0,1)",
                                backgroundColor: "rgba(0,0,0,0)"
                            },
                            {
                                yAxisID: 'fat_rate',
                                label: '体脂肪率(%)',
                                data: self.fat_rateList,
                                borderColor: "rgba(0,255,0,1)",
                                backgroundColor: "rgba(0,0,0,0)"
                            },
                            {
                                yAxisID: 'bmi',
                                label: 'BMI',
                                data: self.bmiList,
                                borderColor: "rgba(0,0,255,1)",
                                backgroundColor: "rgba(0,0,0,0)"
                            },
                        ]
                    },
                    options: {
                        title: {
                            display: true,
                            text: '最近の記録',
                        },
                        elements: {
                            line: {
                                tension: 0,
                            }
                        },
                        scales: {
                            yAxes: [
                                {
                                    id: 'weight',
                                    type: 'linear',
                                    position: 'left',
                                    ticks: {
                                        suggestedMax: 100,
                                        suggestedMin: 0,
                                        stepsize: 10,
                                        callback: function(value, index, values){
                                            return value + 'kg';
                                        }
                                    }
                                },
                                {
                                    id: 'fat_rate',
                                    type: 'linear',
                                    position: 'right',
                                    ticks: {
                                        suggestedMax: 100,
                                        suggestedMin: 0,
                                        stepsize: 10,
                                        callback: function(value, index, values){
                                            return value + '%';
                                        }
                                    }
                                },
                                {
                                    id: 'bmi',
                                    type: 'linear',
                                    position: 'left',
                                    ticks: {
                                        suggestedMax: 100,
                                        suggestedMin: 0,
                                        stepsize: 10,
                                        callback: function(value, index, values){
                                            return value;
                                        }
                                    }
                                },
                            ]
                        }
                    }
                });
            }).catch(function(error){
            });
        }
    }
}
</script>

var ctx = document.getElementById(“weight”);でcanvasの要素を取得し、var myChart = new Chart(ctx, {…})で{…}の部分にデータを入れることで、canvasにグラフが描画されます。

axiosのpostメソッドを使用して、前回作成したAPIを呼び出し、表示するデータを項目ごとにリスト化してchart.jsに渡します。

動作結果はこんな感じ。

思ったよりいい感じです。

【LARAVEL】【ダイエット支援】グラフ表示用のデータを取得する

前回までの状況はこちら。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/diet-mng

これからダッシュボードに表示するグラフを作成するんですが、それに使用するデータの取得を行います。

今回グラフに表示するのは、最近10日間のデータのみ、という想定をしています。

    public function getGraphData($user)
    {
        $datetimes = [];
        for($i = 0; $i < 10 ; $i++) {
            $datetimes[] = date('Y-m-d', strtotime('today - '.$i.' day'));
        }

        return $user->WeightManagements()
                    ->whereIn(DB::raw('date_format(datetime, "%Y-%m-%d")'), $datetimes)
                    ->get();
    }

今回のポイントは特定ユーザに関連するデータのみを対象にする場合、

$user->WeightManagements()->whereIn()

という感じで、リレーションからのSQLビルダ作成、というやり方ができるらしい。

前回のホテル予約管理ではjoinやらwhereやら使いまくっててコードがごちゃごちゃしていたけど、その必要はなかった。

ただし、ここからgroupbyやら統計関連の関数を使用するとうまく行かない。

どうやら、laravelさんがget()の出力内容にuser_idなどの情報を含めているためにグループ化ができないため、SQL実行エラーとなるようです。

なので、一日の最新を取り出す、ということになると、このやり方ではSQLビルダは使用できなくって、PHP側で処理することになります。

今回はめんどくさいので、最新10日のデータ全部、としました。

きちんとtinkerで動作確認したよ。

    /**
     * グラフ用データを取得する
     */
    public function graph(Request $request)
    {
        return response()->json(['datas' => $this->weightManagement->getGraphData(Auth::user())]);
    }
Route::post('api/weight/graph', 'Weight\ApiController@graph');
>>> $rep->getGraphData($user)
=> Illuminate\Database\Eloquent\Collection {#3849
     all: [
       App\Model\WeightManagement {#3843
         id: 1,
         datetime: "2020-05-28 02:14:00",
         weight: "80.00",
         fat_rate: "17.00",
         bmi: "27.00",
         created_at: "2020-05-28 02:14:50",
         updated_at: "2020-05-28 02:14:50",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3840
           user_id: 1,
           weight_management_id: 1,
         },
       },
       App\Model\WeightManagement {#3844
         id: 2,
         datetime: "2020-05-28 02:15:00",
         weight: "80.00",
         fat_rate: "17.00",
         bmi: "27.00",
         created_at: "2020-05-28 02:15:10",
         updated_at: "2020-05-28 02:15:10",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3841
           user_id: 1,
           weight_management_id: 2,
         },
       },
       App\Model\WeightManagement {#3845
         id: 5,
         datetime: "2020-05-28 02:34:00",
         weight: "80.00",
         fat_rate: "17.00",
         bmi: "28.00",
         created_at: "2020-05-28 02:34:49",
         updated_at: "2020-06-01 01:20:56",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3779
           user_id: 1,
           weight_management_id: 5,
         },
       },
       App\Model\WeightManagement {#3846
         id: 9,
         datetime: "2020-05-31 02:44:00",
         weight: "87.00",
         fat_rate: "17.00",
         bmi: "17.00",
         created_at: "2020-05-31 02:44:36",
         updated_at: "2020-05-31 02:44:36",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3834
           user_id: 1,
           weight_management_id: 9,
         },
       },
       App\Model\WeightManagement {#3847
         id: 10,
         datetime: "2020-06-02 02:03:00",
         weight: "87.80",
         fat_rate: "15.90",
         bmi: "27.00",
         created_at: "2020-06-02 02:03:29",
         updated_at: "2020-06-02 02:03:29",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3848
           user_id: 1,
           weight_management_id: 10,
         },
       },
     ],
   }