【flutter_radar_chart】RadarChartをタップしてFeatureのindexを取得する

## やりたいこと
[flutter_radar_chart](https://pub.dev/packages/flutter_radar_chart)で
RadarChartをタップした位置から角度を計算して、どのFeature付近をタップしたか検知したい。

## できたもの
https://twitter.com/yuskey38/status/1505755398279630850

## やったこと
大枠の流れはこんな感じです
① RadarChartの中心とタップした位置から角度を求める関数を作成する
② 360をFeatureの数で割って１辺あたりの角度を求める
③ ②で求めた角度からFetureの範囲を求める
④ ①で求めた角度が③の範囲のどこに入るかを取得する

### ① RadarChartの中心とタップした位置から角度を求める関数を作成する
```dart
int angle(Offset a, Offset b) {
    double r = atan2(b.dy - a.dy, b.dx - a.dx);
    if (r < 0) {
      r = r + 2 * pi;
    }

    return (r * 360 / (2 * pi)).floor();
  }

// 使い方
angle(RadarChartの中心, タップした位置)
```

### ② 360をFeatureの数で割って１辺あたりの角度を求める
Featureが５つの場合は　`360/5 = 72` となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263661/e5603b42-b058-da73-3d63-781f042ae320.png)


### ③ ②で求めた角度からFetureの範囲を求める
まず前提として①で取得できる角度の基準は図の０°の部分で、そこから時計回りに360°になります。
基準をFeature0を基準とした場合に　図の０°は90°の位置にあたります。
今回1辺あたりの角度は７２°なので90°-72°で72°毎に分割した範囲から-18°ずらします。

斜線の範囲をタップすると0を取得したい場合の計算を例にします。
タップする範囲は基準から+-36°の範囲であるためまず32°を計算します。
`1辺あたりの角度/2`
次に始点からどれだけ72°を足したかを計算します。
 `1辺あたりの角度 * (Featureの数 - 2)`
そしてそこから調整のために18°引きます　
`−１８`

min
`(72°/2 + 72° * (５ - 2)) -　18°`

max
`min + 72° -1`

min以上かつmax未満
`２３4° <= angle < 305°`　の場合に０を取得できます。 

2,3,4を取得する範囲の計算方法は
 `1辺あたりの角度 * (Featureの数 - 2)`
を
 `1辺あたりの角度 * (取得するindex - 2)`
に変えるだけで期待する範囲を求めることができます。
例えば2であれば
min
`(72°/2 + 0) -　18°`

max
`min + 72° -1`

min以上かつmax未満
`18° <= angle < 89°`　の場合に2を取得できます。

１を取得する場合のみ360°をまたぐの条件が変わって
min以上またはmax未満となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263661/62148d5d-20a5-4e0f-9048-56d25eb9566e.png)


今回この範囲をいったんMapで保持しています。
```dart
class AngleRange {
  AngleRange({required this.min, required this.max});

  final double min;
  final double max;
}
```

```dart
  final anglePerSide = 360 / numberOfFeatures;
  final offset = 90 - anglePerSide;

  final indexies = List.generate(numberOfFeatures, (index) => index);

  Map<int, AngleRange> intToAngleRangeMap =
      indexies.fold({}, (result, index) {
    switch (index) {
      case 0:
        final min =
            (anglePerSide / 2 + anglePerSide * (numberOfFeatures - 2)) -
                offset;
        result[index] = AngleRange(min: min, max: min + anglePerSide - 1);
        break;
      case 1:
        break;
      default:
        final min = (anglePerSide / 2 + anglePerSide * (index - 2)) - offset;
        result[index] = AngleRange(min: min, max: min + anglePerSide - 1);
        break;
    }

    return result;
  });
```

### ④ ①で求めた角度が③の範囲のどこに入るかを取得する
min以上かつmax未満であればkeyを返し、それ以外の範囲は１を返すようにしています。

```dart
 final result = intToAngleRangeMap.entries.where((e) {
      return e.value.min <= angle && angle <= e.value.max;
    });

    return result.isEmpty ? 1 : result.first.key;
```

## 使い方
```dart
SizedBox(
    height: 300,
    width: 300,
    child: GestureDetector(
        child: RadarChart(
          ticks: ticks,
          features: features,
          data: data,
          reverseAxis: false,
          sides: features.length,
          graphColors: const [Colors.red],
          featuresTextStyle:
              const TextStyle(color: Colors.black, fontSize: 14),
        ),
        onTapDown: (detail) {
          final _angle =
              angle(const Offset(150, 150), detail.localPosition);
          setState(() {
            index = getFeatureIndex(_angle, features.length);
          });
        })),
```

## 参考
https://qiita.com/arthur87/items/23d3c896dafbc8223fd5

## あとがき
今回５角形を例にしていますが何角形でも期待通りの動作でした。
もう少しロジック整理できそうなので、もしこうすればもっと簡単にできる方法あればコメントお願いします！