---
# System prepended metadata

title: Chart.Js
tags: [實作功能]

---

# Chart.Js
###### tags: `實作功能`

[套件官網](https://www.chartjs.org/)

[顯示具體數值，而不是移上去才看的到](https://blog.csdn.net/ALaDingPro/article/details/82590390)

## 結果
[實作GitHub](https://github.com/MoiraHan/Practice_Chart_Js)

![](https://i.imgur.com/HAUPXhl.png)
![](https://i.imgur.com/NxA2UmE.gif)
![](https://i.imgur.com/AU0KkPL.gif)


## 完整的 Code ( 更新於 2020.7.3 )
```
let chartData = {
    labels:response.itemDisplays,
    datasets: [{
        label: null,
        data: response.correctRates,
        backgroundColor: 'rgba(241, 221, 193, 0.5)',
        borderColor: 'rgba(241, 221, 193, 1)',
        pointBorderWidth: 0, // 點寬
        borderWidth: 8, // 線寬
    }]
};

let chartOptions = {
    // 隱藏最上方的 label ( Data 的標籤 ( 其他頁面的角色 ) )
    legend: {
        display: false,
    },
    //responsive: false, // 依照畫布(canvas)調整大小
    maintainAspectRatio: false, // 調整大小時，保持原始畫布的寬高比，若要更改 Size，必須調整成 false。
    scale: {
        // 外圈 Label 的字體尺寸
        pointLabels: {
            fontFamily: "'微軟正黑體', 'Microsoft JhengHei UI'",
            fontSize: 26,
            fontStyle: 'bold', // 粗體
            fontColor: 'black', // 黑色
        },
        gridLines: {
            // 雷達圖底線的顏色，支援多種顏色 ( Ex. color: ['black', 'red', 'orange', 'yellow', 'green', 'blue', 'indigo'] )
            color: 'DarkGray'
        },
        // 刻度
        ticks: {
            // 圖的數值 Range
            min: 0,
            max: 100,
            display: false,
            stepSize: 20
            //backdropColor: 'black', // 更改背景顏色
            //showLabelBackdrop: false, // 畫背景色，預設為 true
        }
    },

    // 防止滑鼠移上去時，數字閃爍
    hover: {
        animationDuration: 0
    },

    // 數值顯示
    animation: {
        onComplete: function () {

            let chartInstance = this.chart,

            ctx = chartInstance.ctx;
            // 以下屬於canvas的屬性（fontSize、fillStyle、textAlign...）
            ctx.font = Chart.helpers.fontString(24, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
            ctx.fillStyle = "black";
            //ctx.textAlign = 'left';
            //ctx.textBaseline = 'top';

            this.data.datasets.forEach(function (dataset, i) {
                let meta = chartInstance.controller.getDatasetMeta(i);
                meta.data.forEach(function (bar, index) {
                    let data = dataset.data[index] + '%'; // 數值顯示的字
                    // 總資料數為偶數
                    if (dataset.data.length % 2 == 0) {
                        // 若為第 1 個項目，值寫下面
                        if (index == 0) {
                            ctx.textAlign = 'center';
                            ctx.textBaseline = 'top';
                        }
                        // 若為下方的項目，值寫上面
                        else if (index == dataset.data.length / 2) {
                            ctx.textAlign = 'center';
                            ctx.textBaseline = 'bottom';
                        }
                        // 若為左方的項目，值寫右邊
                        else if (index > dataset.data.length / 2) {
                            ctx.textAlign = 'left';
                            ctx.textBaseline = 'top';
                        }
                        // 若為右方的項目，值寫左邊
                        else if (index < dataset.data.length / 2) {
                            ctx.textAlign = 'right';
                            ctx.textBaseline = 'top';
                        }
                    }
                    // 總資料數為奇數
                    else {
                        // 若為第 1 個項目，值寫下面
                        if (index == 0) {
                            ctx.textAlign = 'center';
                            ctx.textBaseline = 'top';
                        }
                        // 若為左方的項目，值寫右邊
                        else if (index >= Math.round(dataset.data.length / 2)) {
                            ctx.textAlign = 'left';
                            ctx.textBaseline = 'top';
                        }
                        // 若為右方的項目，值寫左邊
                        else {
                            ctx.textAlign = 'right';
                            ctx.textBaseline = 'top';
                        }                                           
                    }
                    ctx.fillText(data, bar._model.x, bar._model.y - 5);
                });
            });

            // 建立一個隱藏的 image 放在 Chart 畫布後方
            CreateHiddenImgFromCanvas();

            CallReportToPDF(examId, examineeID);
        }
    }
};

let ctx = document.getElementById('myChart_Print');

let myRadarChart = new Chart(ctx, {
    type: 'radar',
    data: chartData,
    options: chartOptions,
    plugins: [{
        beforeInit: function (chart) {
            chart.data.labels.forEach(function (e, i, a) {
                if (/\n/.test(e)) {
                    a[i] = e.split(/\n/);
                }
            });
        }
    }]
});

```
---

## 隱藏畫布的方法 
不可使用 Display: none，
這樣 Chart.Js 畫的時候 Size 寬和高都會為 0 ( 因為會認父元素的 Size )

```
<!--隱藏起來-->
<div id="divOnlyCreateRadarImage" class="chart-container" 
     style="height:600px !important;
            width:810px !important;
            position: absolute; 
            top: -1000px">
    <canvas id="myChart_Print"></canvas>
</div>
```

[參考-charts disappear if rendered in hidden divs](https://stackoverflow.com/questions/31729371/charts-disappear-if-rendered-in-hidden-divs)

---

## 下載畫布的方法

Html
建立個躲起來的區塊 => position: absolute; top: -1000px
```
<div id="divOnlyCreateRadarImage" class="chart-container" 
                     style="height:600px !important;
                            width:810px !important;
                            position: absolute; 
                            top: -1000px">
                    <canvas id="myChart_Print"></canvas>
</div>
```

JS
* 畫完(onComplete)的時候建立一個 image 元素在最下方並隱藏
* CallReportToPDF 將 image 的 base64 字串傳至後端
```
function CreateHiddenImgFromCanvas() {
    let canvas = document.getElementById('myChart_Print');
    let dataUrl = canvas.toDataURL('image/png', 1); // quality = 1 ( 最佳 )
    let imageFoo = document.createElement('img');
    imageFoo.src = dataUrl;
    imageFoo.id = 'imageFromMyChart_Print'
    imageFoo.style.display = 'none';

    document.body.appendChild(imageFoo);
}

function DrawRadarToPrint() {
    var itemDisplays = GetItemDisplays(false);

    let chartData = {
        labels: itemDisplays,
        datasets: [{
            label: null,
            data: GetItemCorrectRates(),
            backgroundColor: 'rgba(241, 221, 193, 0.5)',
            borderColor: 'rgba(241, 221, 193, 1)',
            pointBorderWidth: 0, // 點寬
            borderWidth: 8, // 線寬
        }]
    };

    let chartOptions = {
        // 隱藏最上方的 label ( Data 的標籤 ( 其他頁面的角色 ) )
        legend: {
            display: false,
        },
        //responsive: false, // 依照畫布(canvas)調整大小
        maintainAspectRatio: false, // 調整大小時，保持原始畫布的寬高比，若要更改 Size，必須調整成 false。
        scale: {
            // 外圈 Label 的字體尺寸
            pointLabels: {
                fontFamily: "'微軟正黑體', 'Microsoft JhengHei UI'",
                fontSize: 26,
                fontStyle: 'bold', // 粗體
                fontColor: 'black', // 黑色
            },
            gridLines: {
                // 雷達圖底線的顏色，支援多種顏色 ( Ex. color: ['black', 'red', 'orange', 'yellow', 'green', 'blue', 'indigo'] )
                color: 'DarkGray'
            },
            // 刻度
            ticks: {
                // 圖的數值 Range
                min: 0,
                max: 100,
                display: false,
                stepSize: 20
                //backdropColor: 'black', // 更改背景顏色
                //showLabelBackdrop: false, // 畫背景色，預設為 true
            }
        },

        // 防止滑鼠移上去時，數字閃爍
        hover: {
            animationDuration: 0
        },

        // 數值顯示
        animation: {
            onComplete: function () {
                showLoading();

                let chartInstance = this.chart,

                ctx = chartInstance.ctx;
                // 以下屬於canvas的屬性（fontSize、fillStyle、textAlign...）
                ctx.font = Chart.helpers.fontString(24, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
                ctx.fillStyle = "black";
                ctx.textAlign = 'center';
                ctx.textBaseline = 'top';

                this.data.datasets.forEach(function (dataset, i) {
                    let meta = chartInstance.controller.getDatasetMeta(i);
                    meta.data.forEach(function (bar, index) {
                        let data = dataset.data[index] + '%'; // 數值顯示的字
                        ctx.fillText(data, bar._model.x, bar._model.y - 5);
                    });
                });

                // 建立一個隱藏的 image 放在 Chart 畫布後方
                CreateHiddenImgFromCanvas();

                // 畫完之後在將區塊隱藏 ( 直接隱藏，ChartJs 就不會畫了 )
                $('#divOnlyCreateRadarImage').hide();

                hideLoading();

                CallReportToPDF();

                //window.close();
            }
        }
    };

    let ctx = document.getElementById('myChart_Print');

    let myRadarChart = new Chart(ctx, {
        type: 'radar',
        data: chartData,
        options: chartOptions,
        plugins: [{
            beforeInit: function (chart) {
                chart.data.labels.forEach(function (e, i, a) {
                    if (/\n/.test(e)) {
                        a[i] = e.split(/\n/);
                    }
                });
            }
        }]
    });
}

function CallReportToPDF() {
      var image = document.getElementById("myChart_Print").toDataURL("image/png",1.0);
        image = image.replace('data:image/png;base64,', '');

        var data = { examId: '@Model.ExamId', examineeID: '@Model.ExamineeInfo.ID', strImage_Base64: image };
        post_to_url('@Url.Action("ReportToPDF")', data, "POST");
}

function post_to_url(path, params, method) {
    showLoading();
    method = method || "post"; // Set method to post by default, if not specified.

    // The rest of this code assumes you are not using a library.
    // It can be made less wordy if you use one.
    var form = document.createElement("form");
    form.setAttribute("method", method);
    form.setAttribute("action", path);
    form.id = 'formSubmitToPDF';
    form.target = '_blank';

    for (var key in params) {
        var hiddenField = document.createElement("input");
        hiddenField.setAttribute("type", "hidden");
        hiddenField.setAttribute("name", key);
        hiddenField.setAttribute("value", params[key]);
        form.appendChild(hiddenField);
    }

    document.body.appendChild(form); // Not entirely sure if this is necessary
    form.submit();

    $("#formSubmitToPDF").remove();
    hideLoading();
}
```

---

## 怎麼讓最大的數值不要遮到 項目Label ?

事發圖
![](https://i.imgur.com/uOdtSNO.png)


```
// 數值顯示
// 動畫完成 ( 就是畫完後的動作 )
animation: {
    onComplete: function () {

        let chartInstance = this.chart,

        ctx = chartInstance.ctx;
        // 以下屬於canvas的屬性（fontSize、fillStyle、textAlign...）
        ctx.font = Chart.helpers.fontString(24, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
        ctx.fillStyle = "black";
        //ctx.textAlign = 'left';
        //ctx.textBaseline = 'top';

        this.data.datasets.forEach(function (dataset, i) {
            let meta = chartInstance.controller.getDatasetMeta(i);
            meta.data.forEach(function (bar, index) {
                let data = dataset.data[index] + '%'; // 數值顯示的字
                // 總資料數為偶數
                if (dataset.data.length % 2 == 0) {
                    // 若為第 1 個項目，值寫下面
                    if (index == 0) {
                        ctx.textAlign = 'center';
                        ctx.textBaseline = 'top';
                    }
                    // 若為下方的項目，值寫上面
                    else if (index == dataset.data.length / 2) {
                        ctx.textAlign = 'center';
                        ctx.textBaseline = 'bottom';
                    }
                    // 若為左方的項目，值寫右邊
                    else if (index > dataset.data.length / 2) {
                        ctx.textAlign = 'left';
                        ctx.textBaseline = 'top';
                    }
                    // 若為右方的項目，值寫左邊
                    else if (index < dataset.data.length / 2) {
                        ctx.textAlign = 'right';
                        ctx.textBaseline = 'top';
                    }
                }
                // 總資料數為奇數
                else {
                    // 若為第 1 個項目，值寫下面
                    if (index == 0) {
                        ctx.textAlign = 'center';
                        ctx.textBaseline = 'top';
                    }
                    // 若為左方的項目，值寫右邊
                    else if (index >= Math.round(dataset.data.length / 2)) {
                        ctx.textAlign = 'left';
                        ctx.textBaseline = 'top';
                    }
                    // 若為右方的項目，值寫左邊
                    else {
                        ctx.textAlign = 'right';
                        ctx.textBaseline = 'top';
                    }                                           
                }
                ctx.fillText(data, bar._model.x, bar._model.y - 5);
            });
        });


        // 圖畫完 還要做啥事都可以放這 ( Ex. 把圖轉 image、下載圖 )
    }
}
```

### 結果
七個項目 ( 奇數項目 )
![](https://i.imgur.com/KLyDMQ2.png)

八個項目 ( 偶數項目 )
![](https://i.imgur.com/q9sW758.png)


---

## 實作進度條 onProgress

### 結果顯示 ( 進度條 和 Loading Modal )
![](https://i.imgur.com/P7ZblRs.gif)

### Html
```
<progress id="animationProgress" max="1" value="0" style="width: 100%"></progress>
```

### Script
```
animation: {
    onProgress: function (animation) {
        showLoading(); // 顯示 loading 的 modal

        // 直到當前步驟 等於 全部步驟才隱藏 loading 的 Modal
        if (animation.currentStep == animation.numSteps)
            hideLoading();

       // 控制進度條的 Value
       document.getElementById('animationProgress').value = animation.currentStep / animation.numSteps;
    },
}
```