# Chart.Js
###### tags: `實作功能`
[套件官網](https://www.chartjs.org/)
[顯示具體數值,而不是移上去才看的到](https://blog.csdn.net/ALaDingPro/article/details/82590390)
## 結果
[實作GitHub](https://github.com/MoiraHan/Practice_Chart_Js)



## 完整的 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 ?
事發圖

```
// 數值顯示
// 動畫完成 ( 就是畫完後的動作 )
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、下載圖 )
}
}
```
### 結果
七個項目 ( 奇數項目 )

八個項目 ( 偶數項目 )

---
## 實作進度條 onProgress
### 結果顯示 ( 進度條 和 Loading Modal )

### 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;
},
}
```