plotlyでレーダーチャートを描く

本記事執筆時点でplotlyにはレーダーチャート作成機能が無いが、色々頑張ればかろうじてレーダーチャートらしきものを作成することはできる。

<img width="387" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/109348/e5ae4470-99b7-afaf-2fb0-12755a80f3cd.png">


元ネタはこちら

- [r - Can I recreate this polar coordinate spider chart in plotly? - Stack Overflow](https://stackoverflow.com/questions/37670412/can-i-recreate-this-polar-coordinate-spider-chart-in-plotly)

# アイデア

基本的な考え方はこうだ。

- データを緯度経度に変換する。
- 南極を中心としてプロット。
- 南極から眺める🤔

インタラクティブなグラフっぽい雰囲気は出るけど、所詮は地図なので地図っぽい拡大縮小になるし、データポイントは緯度経度に変換されてしまっているのでマウスオーバーしてもロクな情報が出ない。

すなわち基本的にはやめておいたほうが良い方法である。
`ggradar`とか`fmsb`とかもっといいものがあるのでそっちを使おう。
従って以下のテキストは読む必要がない。

# 手順

## 使用するライブラリとサンプルデータ

plotlyの他にdata.tableを使っている。data.tableは別に必須ではない。

```r
library(data.table)
library(plotly)
```

データはデータフレームで用意する。今回の方法だと値は全て比率になっていなければならない。少し調整すればなんとかなるだろうが、おそらく先に比率に変換してしまったほうが楽。

```r
d <- data.frame(
  label = c("a", "b", "c", "d", "e"),
  data1 = c(0.1, 0.3, 0.4, 0.8, 0.2),
  data2 = c(0.9, 0.8, 0.7, 0.1, 0.1)
)
```

## データの変換とグリッドラインの生成

まずデータを緯度経度に変換する。緯度は南極で-90度、赤道で0度になることをイメージしつつ変換しよう。南緯80〜75度程度までの範囲を使うといい感じにプロットされるので、scaleというパラメータを導入して調整する。

```r
scale = 15 # 大きさ調整 10〜15くらいが丁度よい様子
# 比率データを緯度に換算する。0が赤道で-90が南極。
# scale=15の場合は比率=1が南緯75度に対応する感じ。
d$lat = scale * d$data1 - 90
d$lat2 = scale * d$data2 - 90
# 各データ列を等分して分配するために360/データ数刻みで分割して経度を計算
d$long = (as.numeric(d$label)-1) * 360/nrow(d)
```

地球にはグリッドラインが描かれていないので、これもこちらで用意してやる必要がある。緯線に平行なヤツと経線に平行なヤツを別々に準備するのがコツだ。

```r
# 10%, 25%, 50%, 75%, 100%に対応する緯度データ
gridlines1 = data.table(lat = -90 + scale*(c(0.1, 0.25, 0.5, 0.75, 1)))
# 各緯度に対して1周分の経度データを生成
gridlines1 = gridlines1[, .(long = c(seq(0, 360, length.out=100))), by = lat]
## 経線に平行なグリッドの生成 ----
# データ列数に対応する経度データ
gridlines2 = data.table(long = seq(0, 360, length.out=nrow(d)+1)[-1])
# 南極点と外周を繋ぐようにデータを追加
gridlines2 = gridlines2[, .(lat = c(-90, -90+scale)), by = long]

# グリッドに対応するテキストデータを生成
text.labels = data.table(
  lat  = seq(-90, -90+scale, length.out = 5),
  long = 0,
  text = c("", "25%", "50%", "75%", "100%")
)
```

## プロット

最初に`plot_ly()`を呼び出したら順次`add_trace()`で追加していく。

```r
# グリッドを描画するための線種指定
l1 <- list(width = 0.5, color = rgb(.5,.5,.5), dash = "3px")

# プロット
plot_ly() %>%
  add_trace( # データのプロット
    data = d[c(1:5, 1),], # ラインを一周させるために最初の項目を重複させる
    type = "scattergeo",
    lat=~lat, lon=~long,
    mode = "lines+markers",
    name = "データ1",
    hoverinfo = "none"
    ) %>%
  add_trace( # データ2のプロット
    data = d[c(1:5, 1), ],
    type = "scattergeo",
    lat = ~lat2, lon = ~long,
    mode = "lines+markers",
    name = "データ2",
    hoverinfo = "none"
  ) %>%
  add_trace( # グリッドラインのプロット
    data = gridlines1,
    type = "scattergeo",
    lat=~lat, lon=~long,
    mode = "lines",
    line = l1,
    showlegend = FALSE,
    inherit = FALSE,
    hoverinfo = "none"
  ) %>%
  add_trace( # グリッドライン2のプロット
    data = gridlines2,
    type = "scattergeo",
    lat = ~lat, lon = ~long,
    mode = "lines",
    line = l1,
    showlegend = FALSE,
    inherit = FALSE,
    hoverinfo = "none"
  ) %>%
  add_trace( # グリッドに対応するラベルの追加
    data = text.labels,
    type = "scattergeo", mode = "text",
    lat = ~lat, lon = ~long,
    text = ~text,
    textfont = list(size=12, color = "grey"),
    showlegend = FALSE,
    inherit = FALSE
  ) %>%
  add_trace( # 項目ラベルの追加
    data = d,
    lat = -90+scale*1.1, # 最外周のちょっと外を指定
    lon = ~long,
    type = "scattergeo", mode = "text",
    text = ~label,
    textfont = list(size = 12, color = "grey"),
    showlegend = FALSE, inherit = FALSE
  ) %>%
  layout( # レイアウト調整
    geo = list(
      showcoastlines=FALSE,   # 海岸線を消す（地図では！ないので！！！）
      showframe=FALSE,        # 枠を消す（お好みで）
      projection = list(
        type = "azimuthal equidistant", # 正距方位図法を指定（重要）
        rotation = list(lat=-90),
        scale = 5
      )),
    legend = list(x = 0.7, y = 0.85)
  )
```

ポイントはいくつかあるが、次の点に注意すれば概ねレーダーチャートらしいものができる。

- データポイントをプロットする際には、ラインを一周させるために最初の項目を重複させるように元データを指定する。
- `hoverinfo`は何の役にも立たないので非表示にしておく。
- `layout()`では海岸線を消す(`showcoastlines=FALSE`)のと地図の種類を正距方位図法(`azimuthal equidistant`)を指定すること。そうしないとただの地図になる。