FastAPIにリクエストしてRで作ったグラフ画像を返すサーバーをDockerコンテナ(プロトタイプ第１号)

# 初めに
題名の通り。もう少し改良は加えていきたいが、基本的なことはこの仕組みで実行できる。
Rのイメージにpythonをaptでインストールしている。他にもっと良いやり方はあるかも、、、

# 環境
Windows10 + Docker DeskTop(WSL2 - Ubuntu 20.04.3 LTS)

```shell
docker -v
Docker version 20.10.11, build dea9396
```

## フォルダ構成
```
fastapi
 ├ Dockerfile
 └ app
   ├ main.py
   └ rexec
     └ graph.py
```

## ファイル
```Dockerfile:Dockerfile
# R-4.1.2
FROM r-base:4.1.2

# ダウンロード先のミラーサイト設定(53のJapanを選択)
RUN R -e 'chooseCRANmirror(ind=53)'

# ggplot2のパッケージをインストール
RUN R -e 'install.packages("ggplot2")'
# shinyのパッケージをインストール
RUN R -e 'install.packages("shiny", dependencies = TRUE)'

# python-3.9
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y python3.9 python3-pip python3-setuptools python3-dev

# pipのupgrade
RUN pip3 install -U pip

# fastapiとuvicornのインストール
RUN pip3 install fastapi uvicorn pyper

# ポートはデフォルトの8000で利用
EXPOSE 8000

# デフォルトのディレクトリ設定
WORKDIR /app

# サーバー起動
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```

```python:main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import json
import uvicorn
import os
from rexec import graph

app = FastAPI()

# 許可するドメインを設定
origins = [
    # "http://domainname.com",
    # "https://domainname.com",
    "http://localhost",
    "http://localhost:3000",
]

# クロス設定
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/api/graph/{type}")
def read_item(type: int):
    base64image = graph.getBase64Graph(type)
    return base64image

```



```python:graph.py
import pyper
import os


def getBase64Graph(type: int = 1):

    r = pyper.R()
    r(
        """
        # ライブラリインポート
        library(ggplot2)
        library(shiny)
        """
    )
    # typeで出力するグラフを切り替え
    if type == 1:
        r(
            """
            p <- ggplot(data = iris,
                mapping = aes(x=Sepal.Width)) +
                geom_density(fill = "gold")
            """
        )
    if type == 2:
        r(
            """
            p <- ggplot(data = iris,
                        mapping = aes(x=Species, y=Petal.Length, fill=Species)) +
                        geom_boxplot()
            """
        )
    if type == 3:
        r(
            """
            p <- ggplot(data = iris,
                        mapping = aes(x=Sepal.Length, y=Petal.Length)) +
                        geom_point(aes(colour=Species)) +
                        geom_smooth(method = "lm", aes(colour=Species))
            """
        )
    # 画像出力＆base64変換(sample.pngはアクセスorユーザー毎等でユニークな名前にすることを推奨)
    filename = "sample.png"
    r(
        """
        # 画像出力(サイズは dpi*width × dpi*height のピクセルサイズとなる)
        ggsave(filename = "$filename",
            plot = p,
            device = "png",
            width = 6.4, height = 4.8,
            dpi = 100)

        # base64変換
        b64image <- base64enc::dataURI(file = "$filename", mime = "image/png")
        """.replace(
            "$filename", filename
        )
    )
    # base64で取得
    res = r.get("b64image")
    # ファイル削除
    os.remove(filename)

    return res
```

## コンテナ構築シェル
```shell
# dockerファイルのイメージビルド
docker build -t pyrimage .
# コンテナ化(-v appのフォルダ共有、-p 8000ポート使用)
docker run -d --name pyrgraph -v $PWD/app:/app -p 8000:8000 pyrimage
```

# 確認方法
以下のようなReactのページを用意
※axiosはnpmでインポートしてください

```React:App.jp
import { React, useState, useEffect } from "react";
import axios from "axios";

const App = () => {
  const [base64image, setBase64image] = useState(null);
  const baseUrl = "http://localhost:8000/api/graph/";

  const getBase64Image = async (getNo) => {
    const responce = await axios.get(`${baseUrl}${getNo}`);
    setBase64image(responce.data);
  };

  return (
    <>
      <div>
        <button onClick={(e) => getBase64Image(1)}>Graph1</button>
        <button onClick={(e) => getBase64Image(2)}>Graph2</button>
        <button onClick={(e) => getBase64Image(3)}>Graph3</button>
      </div>
      {base64image !== null ? <img src={base64image} /> : null}
    </>
  );
};
export default App;
```

## こんな感じ
![graph.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541739/4d118e94-ad63-512c-2193-cd48a92ee9b3.gif)



# 最後に
image作成に時間がかかるのがイマイチ、、shinyのインストールは課題。base64にしたいだけだからpythonでやった方が良いかも、、
データをポストして、内容に応じたグラフを描画とかしたい。
個人でグラフ画像とか引っ張りたいときは、Jupyterとか使うと簡単にできるんだろうけどね、、

# 参考
https://data-viz-lab.com/ggplot2