From 94dbe1dee6741c7634b103ba6aa4ae18b1210fc9 Mon Sep 17 00:00:00 2001 From: Cindy Chang Date: Tue, 18 Jun 2024 09:12:18 +0800 Subject: [PATCH] WIP: #258 Compare page --- src/module/timeLabel.js | 2 +- src/views/Compare/Dashboard/index.vue | 349 +++++++++++++++++++++++++- 2 files changed, 347 insertions(+), 4 deletions(-) diff --git a/src/module/timeLabel.js b/src/module/timeLabel.js index 074a70e..03b4b0d 100644 --- a/src/module/timeLabel.js +++ b/src/module/timeLabel.js @@ -182,7 +182,7 @@ export function followTimeLabel(second, max, fixedNumber = 0) { result = (second / day).toFixed(fixedNumber) + 'd'; break; case 'h': - if(((second % day) / hour) === 0) { + if((second / hour) === 0) { fixedNumber = 0; } result = (second / hour).toFixed(fixedNumber) + 'h'; diff --git a/src/views/Compare/Dashboard/index.vue b/src/views/Compare/Dashboard/index.vue index 9dead4f..5ebc704 100644 --- a/src/views/Compare/Dashboard/index.vue +++ b/src/views/Compare/Dashboard/index.vue @@ -437,7 +437,7 @@ export default { getBarChart(chartData, content, caller) { const maxX = chartData.x_axis.max; const minX = chartData.x_axis.min; - const getMoment = (time)=> this.$moment(time).format('YYYY/M/D hh:mm:ss'); + const getMoment = (time)=> this.$moment(time).format('YYYY/MM/DD'); const getDateLabel = getDateLabelByMinMaxDate; let datasetsPrimary = chartData.data[0].data; let xDataPrimary; @@ -910,6 +910,349 @@ export default { return [primeVueSetData, primeVueSetOption]; }, + /** + * 建立Case By Task水平長條圖 + * 避免共用 PrimeVue 設定值,避免覆蓋設定 + * @param { object } chartData chart data + * @param { object } content titels 標題的文字 + * @param { boolean } isSingle 單個或雙數 activity + * @param { string } xUnit x 軸單位 + */ + getCaseByTaskHorizontalBarChart(chartData, content, isSingle, xUnit = 'count') { + const maxX = chartData.x_axis.max; + const getSimpleTimeLabel = simpleTimeLabel; + const getFollowTimeLabel = followTimeLabel; + const labelPrimary = chartData.data[0].label; + const labelSecondary = chartData.data[1].label; + let primeVueSetData = {}; + let primeVueSetOption = {}; + + // 大到小排序: Primary 為主 + const datasetsPrimary = chartData.data[0].data; + const datasetsSecondary = chartData.data[1].data; + datasetsPrimary.sort((a, b) => b.y - a.y); + datasetsSecondary.sort((a, b) => { + // Find the index of a.x in data1 + let indexA = datasetsPrimary.findIndex(item => item.x === a.x); + // Find the index of b.x in data1 + let indexB = datasetsPrimary.findIndex(item => item.x === b.x); + + // Compare the indexes + return indexA - indexB; + }); + + // 找出排序後,分別拉出 x, y Data + const xLabelData = datasetsPrimary.map(item => item.x); + const yDataPrimary = datasetsPrimary.map(item => item.y); + const yDataSecondary = datasetsSecondary.map(item => item.y); + + primeVueSetData = { + labels: xLabelData, + datasets: [ + { + label: labelPrimary, + data: yDataPrimary, + backgroundColor: this.colorPrimary, + }, + { + label: labelSecondary, + data: yDataSecondary, + backgroundColor: this.colorSecondary, + }, + ] + }; + primeVueSetOption = { + indexAxis: 'y', + responsive: true, + maintainAspectRatio: false, + layout: { + padding: { + top: 16, + left: 8, + right: 8, + } + }, + plugins: { + legend: false, // 圖例 + tooltip: { + // displayColors: false, + mode: 'index', // 可顯示全部的 data label + titleFont: {weight: 'normal'}, + callbacks: {} + }, + title: { + display: false, + }, + }, + scales: { + x: { + title: { + display: true, + color: '#334155', + font: { + size: 12, + lineHeight: 2 + }, + text: content.x, + }, + ticks: { + display: true, + maxRotation: 0, // 不旋轉 lable 0~50 + color: '#64748b', + callback: function(value, index, ticks) { + return value; // 計算數量,沒有時間單位才對 + } + }, + grid: { + color: '#64748b', + tickLength: 0, // 網格是否超過邊線 + }, + border: { + display:false, + }, + }, + y: { + beginAtZero: true, // scale 包含 0 + type: 'category', + title: { + display: true, + color: '#334155', + font: { + size: 12, + lineHeight: 2 + }, + text: content.y, + }, + ticks:{ + color: '#64748b', + padding: 8, + }, + grid: { + display:false, + color: '#64748b', + }, + border: { + display: false, // 隱藏左側多出來的線 + }, + }, + }, + }; + switch (xUnit) { + case 'count': + default: + primeVueSetOption.scales.x.ticks.precision = 0; // x 軸顯示小數點後 0 位 + primeVueSetOption.plugins.tooltip.callbacks.label = function(context) { + let value = context.parsed.y; + + value = context.parsed.x === null ? "n/a" : context.parsed.x; + switch (context.datasetIndex) { + case 0: // Primary + return `${labelPrimary}: ${value}`; + case 1: // Secondary + return `${labelSecondary}: ${value}`; + } + }; + break; + } + if(isSingle) { // 設定一個活動的 y label、提示框文字 + primeVueSetOption.plugins.tooltip.callbacks.title = function(context) { + return `${content.y}: ${context[0].label}`; + }; + primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) { + let label = xLabelData[index]; + if(label) { + return label.length > 21 ? `${label.substring(0, 18)}...` : label + }; + } + + }else { // 設定「活動」到「活動」的 y label、提示框文字 + primeVueSetOption.plugins.tooltip.callbacks.title = function(context) { + return `${content.y}: ${context[0].label.replace(',', ' - ')}` + }; + primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) { + let label = xLabelData[index]; + let labelStart = label[0]; + let labelEnd = label[1]; + + labelStart = labelStart.length > 10 ? `${labelStart.substring(0,7)}...` : labelStart; + labelEnd = labelEnd.length > 10 ? `${labelEnd.substring(0,7)}...` : labelEnd; + return labelStart + " - " + labelEnd + }; + } + + return [primeVueSetData, primeVueSetOption] + }, + /** + * 建立 Average Processing Time水平長條圖 + * 避免共用 PrimeVue 設定值,避免覆蓋設定 + * @param { object } chartData chart data + * @param { object } content titels 標題的文字 + * @param { boolean } isSingle 單個或雙數 activity + * @param { string } xUnit x 軸單位 'date' | 'count',可傳入以上任一。 + */ + getAvgProcessTimeHorizontalBarChart(chartData, content, isSingle, xUnit="date") { + const maxY = chartData.y_axis.max; + const getSimpleTimeLabel = simpleTimeLabel; + const getFollowTimeLabel = followTimeLabel; + const labelPrimary = chartData.data[0].label; + const labelSecondary = chartData.data[1].label; + let primeVueSetData = {}; + let primeVueSetOption = {}; + + // 大到小排序: Primary 為主 + const datasetsPrimary = chartData.data[0].data; + const datasetsSecondary = chartData.data[1].data; + datasetsPrimary.sort((a, b) => b.y - a.y); + datasetsSecondary.sort((a, b) => { + // Find the index of a.x in data1 + let indexA = datasetsPrimary.findIndex(item => item.x === a.x); + // Find the index of b.x in data1 + let indexB = datasetsPrimary.findIndex(item => item.x === b.x); + + // Compare the indexes + return indexA - indexB; + }); + + // 找出排序後,分別拉出 x, y Data + const xLabelData = datasetsPrimary.map(item => item.x); + const yDataPrimary = datasetsPrimary.map(item => item.y); + const yDataSecondary = datasetsSecondary.map(item => item.y); + + primeVueSetData = { + labels: xLabelData, + datasets: [ + { + label: labelPrimary, + data: yDataPrimary, + backgroundColor: this.colorPrimary, + }, + { + label: labelSecondary, + data: yDataSecondary, + backgroundColor: this.colorSecondary, + }, + ] + }; + primeVueSetOption = { + indexAxis: 'y', + responsive: true, + maintainAspectRatio: false, + layout: { + padding: { + top: 16, + left: 8, + right: 8, + } + }, + plugins: { + legend: false, // 圖例 + tooltip: { + // displayColors: false, + mode: 'index', // 可顯示全部的 data label + titleFont: {weight: 'normal'}, + callbacks: {} + }, + title: { + display: false, + }, + }, + scales: { + x: { + title: { + display: true, + color: '#334155', + font: { + size: 12, + lineHeight: 2 + }, + text: content.x, + }, + ticks: { + display: true, + maxRotation: 0, // 不旋轉 lable 0~50 + color: '#64748b', + }, + grid: { + color: '#64748b', + tickLength: 0, // 網格是否超過邊線 + }, + border: { + display:false, + }, + }, + y: { + beginAtZero: true, // scale 包含 0 + type: 'category', + title: { + display: true, + color: '#334155', + font: { + size: 12, + lineHeight: 2 + }, + text: content.y + }, + ticks:{ + color: '#64748b', + padding: 8, + }, + grid: { + display:false, + color: '#64748b', + }, + border: { + display: false, // 隱藏左側多出來的線 + }, + }, + }, + }; + switch (xUnit) { + case 'date': + default: + primeVueSetOption.plugins.tooltip.callbacks.label = function(context) { + let value = context.parsed.y; + + value = context.parsed.x === null ? "n/a" : getSimpleTimeLabel(context.parsed.x, 2); + switch (context.datasetIndex) { + case 0: // Primary + return `${labelPrimary}: ${value}`; + case 1: // Secondary + return `${labelSecondary}: ${value}`; + } + }; + primeVueSetOption.scales.x.ticks.callback = function (value, index, ticks) { + return getFollowTimeLabel(value, maxY, 1) + }; + break; + } + if(isSingle) { // 設定一個活動的 y label、提示框文字 + primeVueSetOption.plugins.tooltip.callbacks.title = function(context) { + return `${content.y}: ${context[0].label}`; + }; + primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) { + let label = xLabelData[index]; + if(label) { + return label.length > 21 ? `${label.substring(0, 18)}...` : label + }; + } + + }else { // 設定「活動」到「活動」的 y label、提示框文字 + primeVueSetOption.plugins.tooltip.callbacks.title = function(context) { + return `${content.y}: ${context[0].label.replace(',', ' - ')}` + }; + primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) { + let label = xLabelData[index]; + let labelStart = label[0]; + let labelEnd = label[1]; + + labelStart = labelStart.length > 10 ? `${labelStart.substring(0,7)}...` : labelStart; + labelEnd = labelEnd.length > 10 ? `${labelEnd.substring(0,7)}...` : labelEnd; + return labelStart + " - " + labelEnd + }; + } + + return [primeVueSetData, primeVueSetOption] + }, }, async created() { this.isLoading = true; // moubeted 才停止 loading @@ -944,7 +1287,7 @@ export default { this.compareDashboardData.time.avg_cycle_efficiency, this.contentData.avgCycleEfficiency, "Cycle Eff"); [this.avgProcessTimeData, this.avgProcessTimeOptions] = this.getLineChart( this.compareDashboardData.time.avg_process_time, this.contentData.avgProcessTime, 'date'); - [this.avgProcessTimeByTaskData, this.avgProcessTimeByTaskOptions] = this.getHorizontalBarChart( + [this.avgProcessTimeByTaskData, this.avgProcessTimeByTaskOptions] = this.getAvgProcessTimeHorizontalBarChart( this.compareDashboardData.time.avg_process_time_by_task, this.contentData.avgProcessTimeByTask, true, 'date'); [this.avgWaitingTimeData, this.avgWaitingTimeOptions] = this.getLineChart( @@ -958,7 +1301,7 @@ export default { } [this.freqData, this.freqOptions] = this.getLineChart( this.compareDashboardData.freq.cases, this.contentData.freq, 'count'); - [this.casesByTaskData, this.casesByTaskOptions] = this.getHorizontalBarChart( + [this.casesByTaskData, this.casesByTaskOptions] = this.getCaseByTaskHorizontalBarChart( this.compareDashboardData.freq.cases_by_task, this.contentData.casesByTask, true, 'count'); }, mounted() {