Salesforce Visualforce Chart で 近似直線 を実装する

## ■きっかけ
自分の日々の作業の量を比較するため、グラフを使って比較したとき、
「なんとなく忙しいようにみえる」 と 「なんとなく」 でしかわからなかったため、
ひと目で分かる方法がないか、と考えたのがきっかけでした。

また、Salesforce のグラフ上で実現できないか、を考えました。

## ■ケース
毎日行う作業とそれを累計したグラフです。（Google SpreadSheet)
日々の件数が棒グラフ、累計が線グラフ、横軸が日、縦軸が件数です。

| ある月 | べつの月 |
| -- | -- |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309141/7bbd8039-3e99-75f6-7c61-e3eccb92b124.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309141/3083b7fa-8b98-1cec-99a8-ecf2f9471998.png) |

「ある月」と「別の月」を比較すると

- 「ある月」は、初旬はそこまで日々の件数は少なかったものの、中旬ごろから件数が増え、忙しくなったように見えます。
- 「べつの月」は、初旬から忙しく、中旬ごろからは、日々の件数は少なくなり、ゆったりしたように見えます。

あくまでも感覚なので、わかりやすくならないか、考えました。

グラフには、「トレンドライン」という機能があります。
種類でいくつか選ぶことができ、今回は「線形」を選びました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309141/1e453e66-eec3-8c22-2f87-8f58d56912ca.png)


| ある月 | べつの月 |
| -- | -- |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309141/4f1142b5-b8dd-3c49-9257-8f6e2fe9dc17.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309141/64a2467e-588a-2287-581c-944081e5032d.png) |

- 「ある月」のほうが角度が急です。
    - やっぱり、突然いそがしくなった、と言えそうです。
    - ４日ごろから線が発生しているので、そのあたりから忙しくなった、と言えそうです。
- 「べつの月」のほうは角度が緩やかです。

この線、わかりやすいですね。
では、この「トレンドライン」の「線形」のこの線はどうやって求めるのでしょうか。

## ■検証しましょう

この線は「近似直線」というらしいです。
（数学は詳しくないので、細かいことまでわかりませんが汗）

式を求めるため調べました。

こちらのページで理解しました。

https://oshiete.goo.ne.jp/qa/6913614.html

```
y = ax + b
a　=　(ΣXiYi　-　nX'Y')　/　(ΣXi^2　-　nX'^2)
b　=　Y'-aX'  (i=1,2,3・・・)
```

らしいです。わかりやすくします。

```
a = (X×Yの合計 - 標本数 × Xの平均 × Yの平均) ÷ (Xを二乗した合計 - 標本数 × Xの平均の二乗)
b = b = (Yの平均 － a × Xの平均)
```
上記のページで示されている例題で試してみましょう。

| x | y |
|:--:|:--:|
| 10 | 6 |
| 12 | 9 |
| 14 | 10 |
| 16 | 10 |

- 標本数 は、x と y の組み合わせの個数、今回は 4 です。
- x の平均
    - (10 + 12 + 14 + 16) ÷ 4　= 13
- y の平均
    - (6 + 9 + 10 + 10) ÷ 4 = 8.75
- x の２乗 と x×y の合計

| | x | y |　ｘ の 2乗 | x×y |
| -- |:--:|:--:|:--:|:--:|
| | 10 | 6 | 100 | 60 |
| | 12 | 9 | 144 | 108 |
| | 14 | 10 | 196 | 140 |
| | 16 | 10 | 256 | 160 |
| 合計 | - | - | 696 | 468 |

- x の 2乗の合計（ΣXi^2) = 696
- x × y の合計(ΣXiYi) = 468

```
a = (X×Yの合計 - 標本数 × Xの平均 × Yの平均) ÷ (Xを二乗した合計 - 標本数 × Xの平均の二乗)
a = (468 - 4 × 13 × 8.75) ÷ (696 - 4 × 13 × 13)
a = 0.65
```

```
b = (Yの平均 － a × Xの平均)
b = (8.75 - 0.65 × 13)
b = 0.3
```
つまり、

```
y = ax + b
y = 0.65 × x + 0.3
```
となります。

では、上記の式にサンプルに当てはめてみたいと思います。

| x | y |　y = 0.65 × x + 0.3 |
|:--:|:--:|:--:|
| 10 | 6 | 6.8 |
| 12 | 9 | 8.1 |
| 14 | 10 | 9.4 |
| 16 | 10 | 10.7 |

トレンドラインで表示したときのグラフと、この計算で取得した値で作ったグラフを比較してしましょう。

| トレンドライン | 計算して取得した値で作ったグラフ |
| -- | -- |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309141/543df880-c592-4760-1195-9071cd7fa774.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309141/ed2fd768-59ac-cbae-5340-0b8892e034c0.png) |

同じ形になりました。
これで、トレンドラインの線形は、この計算方法で算出できることがわかりました。

## ■実装してみましょう

では、これを Salesforce Visualforce の Chart で実装してみましょう。

基本的な Salesforce Visualforce の Chart の使い方は以下を参考にしてください。
　■Visualforce Charting を使用した複雑なグラフの作成
　　https://developer.salesforce.com/docs/atlas.ja-jp.pages.meta/pages/pages_charting_example.htm

__■近似直線計算クラス__

```apex:ApproximationStraightLine.cls
public class ApproximationStraightLine {
    public Double x {get; set;}
    public Double y {get; set;}
    public Double ay {get; set;}

    public ApproximationStraightLine(Double x, Double y) {
        this.x = x;
        this.y = y;
        this.ay = 0;
    }

    public static List<ApproximationStraightLine> calcurate(List<ApproximationStraightLine> listData) {
        Double numberOfSamples = numberOfSamples(listData);
        Double averageX        = getAverageX(listData);
        Double averageY        = getAverageY(listData);
        Double sigmaXiYi       = getSigmaXiYi(listData);
        Double sigmaXiPow2     = getSigmaXiPow2(listData);

        // a = (ΣXiYi－標本数×Xの平均×Yの平均)÷(ΣXi^2－標本数×Xの平均の二乗)
        Double a = (sigmaXiYi - numberOfSamples * averageX * averageY) / (sigmaXiPow2 - numberOfSamples * Math.pow(averageX, 2));

        // b = (Yの平均－a×Xの平均)
        Double b = averageY - a * averageX;

        for (ApproximationStraightLine data : listData) {
            // y = xa + b
            data.ay = data.x * a + b;
        }

        return listData;
    }

    private static Double numberOfSamples(List<ApproximationStraightLine> listData) {
        Double size = (Double) listData.size();
        return size;
    }

    private static Double getAverageX(List<ApproximationStraightLine> listData) {
        Double total = 0;
        Integer count = 0;

        for (ApproximationStraightLine data : listData) {
            total += data.x;
            count++;
        }

        return total / count;
    }

    private static Double getAverageY(List<ApproximationStraightLine> listData) {
        Double total = 0;
        Integer count = 0;

        for (ApproximationStraightLine data : listData) {
            total += data.y;
            count++;
        }

        return total / count;
    }

    private static Double getSigmaXiYi(List<ApproximationStraightLine> listData) {
        Double value = 0;

        for (ApproximationStraightLine data : listData) {
            value += (data.x * data.y);
        }

        return value;
    }

    private static Double getSigmaXiPow2(List<ApproximationStraightLine> listData) {
        Double value = 0;

        for (ApproximationStraightLine data : listData) {
            value += Math.pow(data.x, 2);
        }

        return value;
    }

}
```

__■Visualforceページのコントロールクラス__　　

```apex:ChartTestController.cls
public class ChartTestController {

    public List<ApproximationStraightLine> getData() {
        List<ApproximationStraightLine> listData = new List<ApproximationStraightLine>();
        listData.add(new ApproximationStraightLine(10, 6));
        listData.add(new ApproximationStraightLine(12, 9));
        listData.add(new ApproximationStraightLine(14, 10));
        listData.add(new ApproximationStraightLine(16, 10));

        return ApproximationStraightLine.calcurate(listData);
    }
}
```

__■Visualforceページ__　　

```html:ChartTest.vfpage
<apex:page controller="ChartTestController">
    <apex:chart height="400" width="700" data="{!data}">
        <apex:axis type="Numeric" position="left" fields="y"  title="Y" grid="true" minimum="0" maximum="12"/>
        <apex:axis type="Category" position="bottom" fields="x"  title="X"/>

        <apex:barSeries orientation="vertical" axis="left"  xField="x" yField="y">
            <apex:chartTips height="20" width="120"/>
        </apex:barSeries>
        <apex:lineSeries axis="left" xField="x" yField="ay"  markerType="circle" markerSize="4" markerFill="#00FF00"/>
    </apex:chart>
</apex:page>
```

同じ近似直線を実現することができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309141/65a580f1-0aee-35c1-72c0-ca55b7ada4f1.png)