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