From 7750f913a8c8dc58a76da65baf31cef620f79de9 Mon Sep 17 00:00:00 2001 From: Cindy Chang Date: Mon, 17 Jun 2024 22:32:57 +0800 Subject: [PATCH] ticks.length solves the Y axis bug. --- src/constants/constants.js | 1 + src/module/timeLabel.js | 12 +- src/views/Discover/Performance/index.vue | 183 +++++++++++++++++++++-- 3 files changed, 173 insertions(+), 23 deletions(-) diff --git a/src/constants/constants.js b/src/constants/constants.js index d413b10..cccd852 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -1,3 +1,4 @@ +export const PRIME_VUE_TICKS_LIMIT = 6; export const knownLayoutChartOption = { padding: { top: 16, diff --git a/src/module/timeLabel.js b/src/module/timeLabel.js index 5adc000..51860dc 100644 --- a/src/module/timeLabel.js +++ b/src/module/timeLabel.js @@ -1,5 +1,4 @@ import moment from 'moment'; -const NUM_OF_PARTS_TO_SPLIT = 8; const TOFIXED_DEICMAL = 1; /** @@ -8,11 +7,10 @@ const TOFIXED_DEICMAL = 1; * @param {number} maxTimeInSecond * @returns {object} {resultStepSize, unitToUse} */ -export const getStepSizeOfYTicks = (maxTimeInSecond) => { - const numOfParts = NUM_OF_PARTS_TO_SPLIT; +export const getStepSizeOfYTicks = (maxTimeInSecond, numOfParts) => { const {unitToUse, timeValue} = getTimeUnitAndValueToUse(maxTimeInSecond); let resultStepSize; - resultStepSize = (timeValue / numOfParts); + resultStepSize = (timeValue * (1.125) / numOfParts); return {resultStepSize, unitToUse}; } @@ -30,17 +28,17 @@ const getTimeUnitAndValueToUse = (secondToDecide) => { const hh = secondToDecide / hour; const mm = secondToDecide / minutes; - if (dd > 0) { + if (dd > 0 && dd > 1) { return { unitToUse: "d", timeValue: secondToDecide / day, }; - } else if (hh > 0) { + } else if (hh > 0 && hh > 1) { return { unitToUse: "h", timeValue: secondToDecide / hour, }; - } else if (mm > 0) { + } else if (mm > 0 && mm > 1) { console.log('mm > 0 && mm > 1', secondToDecide / minutes); return { unitToUse: "m", diff --git a/src/views/Discover/Performance/index.vue b/src/views/Discover/Performance/index.vue index a2b1e49..1dfb35a 100644 --- a/src/views/Discover/Performance/index.vue +++ b/src/views/Discover/Performance/index.vue @@ -153,7 +153,9 @@ import { simpleTimeLabel, followTimeLabel, dateLabel, getYTicksByIndex, } from '@/module/timeLabel.js'; import FreqChart from './FreqChart.vue'; +import { PRIME_VUE_TICKS_LIMIT } from '../../../constants/constants.js'; +const primeVueTicksLimit = PRIME_VUE_TICKS_LIMIT; const knownScaleLineChartOptions = { x: { type: 'time', @@ -196,8 +198,7 @@ const knownScaleLineChartOptions = { }, }, ticks:{ - color: '#64748b', - padding: 8, + }, grid: { color: '#64748b', @@ -399,8 +400,7 @@ export default { }, scales: customizedScaleOption, }; - // resultStepSize: Y 軸一個刻度的高度的純數值部分,unitToUse則可能是 d,h,m,s 四者之一 - const {resultStepSize, unitToUse} = getStepSizeOfYTicks(maxY); + switch (yUnit) { case 'date': primeVueSetOption.plugins.tooltip.callbacks.label = function(context) { @@ -409,8 +409,12 @@ export default { primeVueSetOption.scales.x.min = minX; primeVueSetOption.scales.x.max = maxX; primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) { + // resultStepSize: Y 軸一個刻度的高度的純數值部分,unitToUse則可能是 d,h,m,s 四者之一 + // ticks.length是動態由 PrimeVue 決定的,例如可能是 6 或是 8 + const {resultStepSize, unitToUse} = getStepSizeOfYTicks(maxY, ticks.length); return getYTicksByIndex(resultStepSize, index, unitToUse); }; + primeVueSetOption.scales.y.ticks.stepSize = resultStepSize; break; case 'count': default: @@ -526,6 +530,7 @@ export default { ticks:{ color: '#64748b', padding: 8, + }, grid: { color: '#64748b', @@ -628,6 +633,7 @@ export default { ticks:{ color: '#64748b', padding: 8, + }, grid: { display:false, @@ -755,12 +761,158 @@ export default { return this.$moment(time).format('YYYY/M/D hh:mm:ss') }, /** - * 建立Average Cycle Time折線圖 + * 建立折線圖,其中設定值不是變數,避免同一個畫面中的設定值彼此覆蓋 * @param { object } chartData chart data * @param { object } content titels 標題文字 * @param { string } yUnit y 軸單位 'date' */ - getAvgCycleTimeLineChart(chartData, content, yUnit) { + getExplicitDeclaredLineChart(chartData, content, yUnit) { + const getMoment = (time)=> this.$moment(time).format('YYYY/M/D hh:mm:ss'); + let datasets; + let minX = chartData.x_axis.min; + let maxX = chartData.x_axis.max; + let maxY = chartData.y_axis.max; + let xData; + let primeVueSetData = {}; + let primeVueSetOption = {}; + const getSimpleTimeLabel = simpleTimeLabel; + const getFollowTimeLabel = followTimeLabel; + + datasets = setLineChartData(chartData.data, chartData.x_axis.max, chartData.x_axis.min, false, chartData.y_axis.max, + chartData.y_axis.min); + xData = this.setXLabelsData(chartData.x_axis); + + + // Customize X axis ticks due to different differences between min and max of data group + // Compare page and Performance page share the same logic + const formatToSet = setTimeStringFormatBaseOnTimeDifference(minX, maxX); + const ticksOfXAxis = mapTimestampToAxisTicksByFormat(xData, formatToSet); + primeVueSetData = { + labels: xData, + datasets: [ + { + label: content.title, + data: datasets, + fill: false, + tension: 0, // 貝茲曲線張力 + borderColor: '#0099FF', + } + ] + }; + primeVueSetOption = { + responsive: true, + maintainAspectRatio: false, + layout: { + padding: { + top: 16, + left: 8, + right: 8, + } + }, + plugins: { + legend: false, // 圖例 + tooltip: { + displayColors: false, + titleFont: {weight: 'normal'}, + callbacks: { + title: function(context) { + return `${content.x}: ${getMoment(context[0].parsed.x)}`; + } + }, + }, + title: { + display: false, + }, + }, + scales: { + x: { + min: minX, + max: maxX, + type: 'time', + title: { + display: true, + color: '#334155', + font: { + size: 12, + lineHeight: 2 + }, + text: content.x, + }, + time: { + displayFormats: { + second: 'h:mm:ss', // ex: 1:11:11 + minute: 'M/d h:mm', // ex: 1/1 1:11 + hour: 'M/d h:mm', // ex: 1/1 1:11 + day: 'M/d h', // ex: 1/1 1 + month: 'y/M/d', // ex: 1911/1/1 + }, + round: true + }, + ticks: { + maxTicksLimit: primeVueTicksLimit, + padding: 8, + display: true, + maxRotation: 0, // 不旋轉 lable 0~50 + color: '#64748b', + source: 'labels', // 依比例彈性顯示 label 數量 + callback: function(value, index) { + return ticksOfXAxis[index]; + }, + }, + border: { + color: '#64748b', + }, + }, + y: { + beginAtZero: true, // scale 包含 0 + title: { + display: true, + color: '#334155', + font: { + size: 12, + lineHeight: 2 + }, + text: content.y + }, + grid: { + color: '#64748b', + }, + border: { + display: false, // 隱藏左側多出來的線 + }, + ticks: { + + color: '#64748b', + padding: 8, + callback: function (value, index, ticks) { + // resultStepSize: Y 軸一個刻度的高度的純數值部分,unitToUse則可能是 d,h,m,s 四者之一 + const {resultStepSize, unitToUse} = getStepSizeOfYTicks(maxY, ticks.length); + return getYTicksByIndex(resultStepSize, index, unitToUse); + }, + } + }, + }, + plugins: { + tooltip: { + callbacks:{ + label:function(context) { + return `${content.y}: ${getSimpleTimeLabel(context.parsed.y, 2)}`; + }, + }, + }, + }, + }; + + return [primeVueSetData, primeVueSetOption] + }, + /** + * 建立Average Waiting Time折線圖 + * @param { object } chartData chart data + * @param { object } content titels 標題文字 + * @param { string } yUnit y 軸單位 'date' + */ + getAvgWaitingTimeLineChart(chartData, content, yUnit) { + const getMoment = (time)=> this.$moment(time).format('YYYY/M/D hh:mm:ss'); let datasets; let minX = chartData.x_axis.min; let maxX = chartData.x_axis.max; @@ -810,7 +962,7 @@ export default { titleFont: {weight: 'normal'}, callbacks: { title: function(context) { - return `${content.x}: ${this.getMoment(context[0].parsed.x)}`; + return `${content.x}: ${getMoment(context[0].parsed.x)}`; } }, }, @@ -866,10 +1018,6 @@ export default { }, text: content.y }, - ticks:{ - color: '#64748b', - padding: 8, - }, grid: { color: '#64748b', }, @@ -877,15 +1025,18 @@ export default { display: false, // 隱藏左側多出來的線 }, ticks: { + + color: '#64748b', + padding: 8, callback: function (value, index, ticks) { // resultStepSize: Y 軸一個刻度的高度的純數值部分,unitToUse則可能是 d,h,m,s 四者之一 - const {resultStepSize, unitToUse} = getStepSizeOfYTicks(maxY); + const {resultStepSize, unitToUse} = getStepSizeOfYTicks(maxY, ticks.length); return getYTicksByIndex(resultStepSize, index, unitToUse); }, } }, }, - plugin: { + plugins: { tooltip: { callbacks:{ label:function(context) { @@ -926,15 +1077,15 @@ export default { } this.casesByTaskHeight = await this.getHorizontalBarHeight(this.performanceData.freq.cases_by_task); // create chart - [this.avgCycleTimeData, this.avgCycleTimeOptions] = this.getAvgCycleTimeLineChart(this.performanceData.time.avg_cycle_time, + [this.avgCycleTimeData, this.avgCycleTimeOptions] = this.getExplicitDeclaredLineChart(this.performanceData.time.avg_cycle_time, this.contentData.avgCycleTime, 'date'); [this.avgCycleEfficiencyData, this.avgCycleEfficiencyOptions] = this.getBarChart( this.performanceData.time.avg_cycle_efficiency, this.contentData.avgCycleEfficiency); - [this.avgProcessTimeData, this.avgProcessTimeOptions] = this.getLineChart(this.performanceData.time.avg_process_time, + [this.avgProcessTimeData, this.avgProcessTimeOptions] = this.getExplicitDeclaredLineChart(this.performanceData.time.avg_process_time, this.contentData.avgProcessTime, 'date'); [this.avgProcessTimeByTaskData, this.avgProcessTimeByTaskOptions] = this.getHorizontalBarChart( this.performanceData.time.avg_process_time_by_task, this.contentData.avgProcessTimeByTask, true, 'date'); - [this.avgWaitingTimeData, this.avgWaitingTimeOptions] = this.getLineChart( + [this.avgWaitingTimeData, this.avgWaitingTimeOptions] = this.getExplicitDeclaredLineChart( this.performanceData.time.avg_waiting_time, this.contentData.avgWaitingTime, 'date'); if(this.performanceData.time.avg_waiting_time_by_edge !== null) { [this.avgWaitingTimeByEdgeData, this.avgWaitingTimeByEdgeOptions] = this.getHorizontalBarChart(