WIP customizing x tick according to time difference

This commit is contained in:
Cindy Chang
2024-06-07 10:37:28 +08:00
parent 92160fab54
commit fc99bac449
3 changed files with 151 additions and 82 deletions

View File

@@ -215,3 +215,14 @@ export const setTimeStringFormatBaseOnTimeDifference = (minTimeStamp, maxTimeSta
return dateFormat;
};
/**
* Converts an array of Unix timestamps to formatted date strings based on the
* specified format for use as axis ticks.
* 根據指定的格式將 Unix 時間戳數組轉換為軸標籤的格式化日期字符串。
* @param {Array<number>} timeStampArr
* @param {string} timeFormat For example, 'MM/DD'
*/
export const mapTimestampToAxisTicksByFormat = (timeStampArr, timeFormat) => {
return timeStampArr.map(ts => moment(ts).format(timeFormat));
};

View File

@@ -9,7 +9,7 @@
<p class="h1">{{ i18next.t("Compare.timeUsage") }}</p>
<ul class="list-disc list-inside text-sm leading-5 pl-3">
<li v-for="(item, index) in timeUsageData" :key="index" :class="{active: isActive === item.tagId}"
@click="isActive = item.tagId" class="cursor-pointer hover:text-primary"><a :href="item.tagId">
@click="isActive = item.tagId" class="cursor-pointer hover:text-primary"><a :href="item.tagId">
{{ item.label }}
</a>
</li>
@@ -19,9 +19,10 @@
<p class="h1">{{ i18next.t("Compare.frequency") }}</p>
<ul class="list-disc list-inside text-sm leading-5 pl-3">
<li v-for="(item, index) in frequencyData" :key="index" :class="{active: isActive === item.tagId}"
@click="isActive = item.tagId" class="cursor-pointer hover:text-primary"><a :href="item.tagId">
{{ item.label }}
</a></li>
@click="isActive = item.tagId" class="cursor-pointer hover:text-primary">
<a :href="item.tagId">
{{ item.label }}
</a></li>
</ul>
</div>
</section>
@@ -48,9 +49,10 @@
<span class="block font-bold text-sm leading-loose text-center my-2">
{{ contentData.avgCycleEfficiency.title }}
<span class="material-symbols-outlined align-middle ml-2 cursor-pointer !text-base"
v-tooltip.bottom="tooltip.avgCycleEfficiency">
{{ i18next.t("Compare.info") }}
</span></span>
v-tooltip.bottom="tooltip.avgCycleEfficiency">
{{ i18next.t("Compare.info") }}
</span>
</span>
<Chart type="bar" :data="avgCycleEfficiencyData" :options="avgCycleEfficiencyOptions" class="h-96" />
</li>
</ul>
@@ -68,8 +70,9 @@
<span class="block font-bold text-sm leading-loose text-center my-2">
{{ contentData.avgProcessTimeByTask.title }}
</span>
<Chart type="bar" :data="avgProcessTimeByTaskData" :options="avgProcessTimeByTaskOptions" class="h-[500px]"
:style="{ height: avgProcessTimeByTaskHeight }"/>
<Chart type="bar" :data="avgProcessTimeByTaskData" :options="avgProcessTimeByTaskOptions"
class="h-[500px]"
:style="{ height: avgProcessTimeByTaskHeight }"/>
</li>
</ul>
</li>
@@ -152,7 +155,10 @@ import LoadingStore from '@/stores/loading.js';
import CompareStore from '@/stores/compare.js';
import SidebarStates from '@/components/Compare/SidebarStates.vue';
import { setLineChartData } from '@/module/setChartData.js';
import { simpleTimeLabel, followTimeLabel, dateLabel } from '@/module/timeLabel.js';
import { simpleTimeLabel, followTimeLabel, dateLabel,
setTimeStringFormatBaseOnTimeDifference,
mapTimestampToAxisTicksByFormat,
} from '@/module/timeLabel.js';
import i18next from '@/i18n/i18n';
const knownLayoutChartOption = {
@@ -403,7 +409,7 @@ export default {
* 手刻折線圖 x label 時間刻度
* @param { object } valueData {min: '2022-02-20T19:54:12', max: '2023-11-27T07:21:53'}
*/
xLabelsData(valueData) {
setXLabelsData(valueData) {
let min = new Date(valueData.min).getTime();
let max = new Date(valueData.max).getTime();
let numPoints = 12;
@@ -434,7 +440,7 @@ export default {
* @param { object } chartData chart data
* @param { object } content titels 標題文字
* @param { string } yUnit y 軸單位 'date' | 'count',可傳入以上任一。
* @returns { [setData, setOption] } 這兩者為符合 PriveVue 圖表格式的數據資料
* @returns { [primeVueSetData, primeVueSetOption] } 這兩者為符合 primeVue 圖表格式的數據資料
*/
getLineChart(chartData, content, yUnit) {
let datasetsPrimary;
@@ -442,33 +448,45 @@ export default {
let minX = chartData.x_axis.min;
let maxX = chartData.x_axis.max;
let maxY = chartData.y_axis.max;
let xData;
let xLabelData;
let labelPrimary = chartData.data[0].label;
let labelSecondary = chartData.data[1].label;
let setData = {};
let setOption = {};
let primeVueSetData = {};
let primeVueSetOption = {};
const getMoment = (time)=> {
return this.$moment(time).format('YYYY/M/D hh:mm:ss')
};
const getSimpleTimeLabel = simpleTimeLabel;
const getFollowTimeLabel = followTimeLabel;
// fabricate x label according to y unit
switch (yUnit) {
case 'date':
datasetsPrimary = setLineChartData(chartData.data[0].data, chartData.x_axis.max, chartData.x_axis.min, false,
chartData.y_axis.max, chartData.y_axis.min);
datasetsSecondary = setLineChartData(chartData.data[1].data, chartData.x_axis.max, chartData.x_axis.min, false,
chartData.y_axis.max, chartData.y_axis.min);
xData = this.xLabelsData(chartData.x_axis);
xLabelData = this.setXLabelsData(chartData.x_axis);
break;
case 'count': // 次數 10 個點
datasetsPrimary = chartData.data[0].data;
datasetsSecondary = chartData.data[1].data;
xData = chartData.data[0].data.map(item => new Date(item.x).getTime());
xLabelData = chartData.data[0].data.map(item => new Date(item.x).getTime());
break;
}
setData = {
labels: xData,
// Customize X axis ticks due to different differences between min and max of data group
const formatToSet = setTimeStringFormatBaseOnTimeDifference(minX, maxX);
const ticksOfXAxis = mapTimestampToAxisTicksByFormat(xLabelData, formatToSet);
const customizedScaleOption = this.getCustomizedScaleOption(
knownScaleLineChartOptions, {
customizeOptions: {
content, ticksOfXAxis,
}
});
primeVueSetData = {
labels: xLabelData,
datasets: [
{
label: labelPrimary,
@@ -488,7 +506,7 @@ export default {
}
]
};
setOption = {
primeVueSetOption = {
responsive: true,
maintainAspectRatio: false,
layout: {
@@ -514,11 +532,11 @@ export default {
display: false,
},
},
scales: this.customizeScaleChartOptionTitleByContent(knownScaleLineChartOptions, content),
scales: customizedScaleOption,
};
switch (yUnit) {
case 'date':
setOption.plugins.tooltip.callbacks.label = function(context) {
primeVueSetOption.plugins.tooltip.callbacks.label = function(context) {
let value = getSimpleTimeLabel(context.parsed.y, 2);
switch (context.datasetIndex) {
@@ -528,15 +546,15 @@ export default {
return `${labelSecondary}: ${value}`;
}
};
setOption.scales.x.min = minX;
setOption.scales.x.max = maxX;
setOption.scales.y.ticks.callback = function (value, index, ticks) {
primeVueSetOption.scales.x.min = minX;
primeVueSetOption.scales.x.max = maxX;
primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) {
return getFollowTimeLabel(value, maxY, 1)
}
break;
case 'count':
setOption.scales.y.ticks.precision = 0; // y 軸顯示小數點後 0 位
setOption.plugins.tooltip.callbacks.label = function(context) {
primeVueSetOption.scales.y.ticks.precision = 0; // y 軸顯示小數點後 0 位
primeVueSetOption.plugins.tooltip.callbacks.label = function(context) {
let value = context.parsed.y;
switch (context.datasetIndex) {
@@ -549,7 +567,7 @@ export default {
break;
}
return [setData, setOption]
return [primeVueSetData, primeVueSetOption]
},
/**
* 建立長條圖
@@ -569,8 +587,8 @@ export default {
let xDataSecondary;
let yDataSecondary;
let labelSecondary = chartData.data[1].label;
let setData = {};
let setOption = {};
let primeVueSetData = {};
let primeVueSetOption = {};
// 轉為百分比
datasetsPrimary = datasetsPrimary.map(value => {
@@ -590,7 +608,7 @@ export default {
xDataSecondary = datasetsSecondary.map(i => i.x);
yDataSecondary = datasetsSecondary.map(i => i.y);
setData = {
primeVueSetData = {
labels: xDataPrimary,
datasets: [
{
@@ -605,7 +623,7 @@ export default {
},
]
};
setOption = {
primeVueSetOption = {
responsive: true,
maintainAspectRatio: false,
layout: knownLayoutChartOption,
@@ -644,7 +662,7 @@ export default {
scales: this.customizeScaleChartOptionTitleByContent(knownScaleBarChartOptions, content),
};
return [setData, setOption]
return [primeVueSetData, primeVueSetOption]
},
/**
* 建立水平長條圖
@@ -659,8 +677,8 @@ export default {
const getFollowTimeLabel = followTimeLabel;
const labelPrimary = chartData.data[0].label;
const labelSecondary = chartData.data[1].label;
let setData = {};
let setOption = {};
let primeVueSetData = {};
let primeVueSetOption = {};
// 大到小排序: Primary 為主
const datasetsPrimary = chartData.data[0].data;
@@ -677,12 +695,12 @@ export default {
});
// 找出排序後,分別拉出 x, y Data
const xData = datasetsPrimary.map(item => item.x);
const xLabelData = datasetsPrimary.map(item => item.x);
const yDataPrimary = datasetsPrimary.map(item => item.y);
const yDataSecondary = datasetsSecondary.map(item => item.y);
setData = {
labels: xData,
primeVueSetData = {
labels: xLabelData,
datasets: [
{
label: labelPrimary,
@@ -696,7 +714,7 @@ export default {
},
]
};
setOption = {
primeVueSetOption = {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
@@ -723,7 +741,7 @@ export default {
};
switch (xUnit) {
case 'date':
setOption.plugins.tooltip.callbacks.label = function(context) {
primeVueSetOption.plugins.tooltip.callbacks.label = function(context) {
let value = context.parsed.y;
value = context.parsed.x === null ? "n/a" : getSimpleTimeLabel(context.parsed.x, 2);
@@ -734,13 +752,13 @@ export default {
return `${labelSecondary}: ${value}`;
}
};
setOption.scales.x.ticks.callback = function (value, index, ticks) {
primeVueSetOption.scales.x.ticks.callback = function (value, index, ticks) {
return getFollowTimeLabel(value, maxY, 1)
};
break;
case 'count':
setOption.scales.x.ticks.precision = 0; // x 軸顯示小數點後 0 位
setOption.plugins.tooltip.callbacks.label = function(context) {
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;
@@ -754,22 +772,22 @@ export default {
break;
}
if(isSingle) { // 設定一個活動的 y label、提示框文字
setOption.plugins.tooltip.callbacks.title = function(context) {
primeVueSetOption.plugins.tooltip.callbacks.title = function(context) {
return `${content.y}: ${context[0].label}`;
};
setOption.scales.y.ticks.callback = function (value, index, ticks) {
let label = xData[index];
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、提示框文字
setOption.plugins.tooltip.callbacks.title = function(context) {
primeVueSetOption.plugins.tooltip.callbacks.title = function(context) {
return `${content.y}: ${context[0].label.replace(',', ' - ')}`
};
setOption.scales.y.ticks.callback = function (value, index, ticks) {
let label = xData[index];
primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) {
let label = xLabelData[index];
let labelStart = label[0];
let labelEnd = label[1];
@@ -779,9 +797,28 @@ export default {
};
}
return [setData, setOption]
return [primeVueSetData, primeVueSetOption]
},
/**
*
* @param whichScaleObj PrimeVue scale option object to reference to
* @param customizeOptions
* @param customizeOptions.content
* @param customizeOptions.ticksOfXAxis
*/
getCustomizedScaleOption(whichScaleObj, {customizeOptions: {
content,
ticksOfXAxis,
},
}) {
let resultScaleObj;
resultScaleObj = this.customizeScaleChartOptionTitleByContent(whichScaleObj, content);
resultScaleObj = this.customizeScaleChartOptionTicks(resultScaleObj, ticksOfXAxis);
return resultScaleObj;
},
/**
* 在一個基本的物件上加以客製化這個物件,客製化的參照來源是 content 的內容
* 之所以有辦法這樣撰寫,是因為我們知道物件的順序是先 x 再 title 再 text
* This function alters the title property of known scales object of Chart option
* This is based on the fact that we know the order must be x -> title -> text.
* @param {object} whichScaleObj PrimeVue scale option object to reference to
@@ -812,7 +849,28 @@ export default {
}
}
};
}
},
/**
*
* @param {object} scaleObjectToAlter this object follows the format of prive vue chart
* @param {Array<string>} ticksOfXAxis For example, ['05/06', '05,07', '05/08']
* or ['08:03:01', '08:11:18', '09:03:41', ], and so on.
*/
customizeScaleChartOptionTicks(scaleObjectToAlter, ticksOfXAxis) {
return {
...scaleObjectToAlter,
x: {
...scaleObjectToAlter.x,
ticks: {
...scaleObjectToAlter.x.ticks,
callback: function(value, index) {
// console.log('根據不同的級距客製化 x 軸的時間刻度');
return ticksOfXAxis[index];
},
},
},
};
},
},
async created() {
this.isLoading = true; // moubeted 才停止 loading

View File

@@ -224,7 +224,7 @@ export default {
* 手刻折線圖 x label 時間刻度
* @param { object } valueData {min: '2022-02-20T19:54:12', max: '2023-11-27T07:21:53'}
*/
xLabelsData(valueData) {
setXLabelsData(valueData) {
let min = new Date(valueData.min).getTime();
let max = new Date(valueData.max).getTime();
let numPoints = 12;
@@ -260,8 +260,8 @@ export default {
let maxX = chartData.x_axis.max;
let maxY = chartData.y_axis.max;
let xData;
let setData = {};
let setOption = {};
let primeVueSetData = {};
let primeVueSetOption = {};
const getMoment = (time)=> {
return this.$moment(time).format('YYYY/M/D hh:mm:ss')
};
@@ -272,14 +272,14 @@ export default {
case 'date':
datasets = setLineChartData(chartData.data, chartData.x_axis.max, chartData.x_axis.min, false, chartData.y_axis.max,
chartData.y_axis.min);
xData = this.xLabelsData(chartData.x_axis);
xData = this.setXLabelsData(chartData.x_axis);
break;
case 'count': // 次數 10 個點
datasets = chartData.data;
xData = chartData.data.map(item => new Date(item.x).getTime());
break;
}
setData = {
primeVueSetData = {
labels: xData,
datasets: [
{
@@ -291,7 +291,7 @@ export default {
}
]
};
setOption = {
primeVueSetOption = {
responsive: true,
maintainAspectRatio: false,
layout: {
@@ -374,24 +374,24 @@ export default {
};
switch (yUnit) {
case 'date':
setOption.plugins.tooltip.callbacks.label = function(context) {
primeVueSetOption.plugins.tooltip.callbacks.label = function(context) {
return `${content.y}: ${getSimpleTimeLabel(context.parsed.y, 2)}`;
};
setOption.scales.x.min = minX;
setOption.scales.x.max = maxX;
setOption.scales.y.ticks.callback = function (value, index, ticks) {
primeVueSetOption.scales.x.min = minX;
primeVueSetOption.scales.x.max = maxX;
primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) {
return getFollowTimeLabel(value, maxY, 1)
};
break;
case 'count':
setOption.scales.y.ticks.precision = 0; // y 軸顯示小數點後 0 位
setOption.plugins.tooltip.callbacks.label = function(context) {
primeVueSetOption.scales.y.ticks.precision = 0; // y 軸顯示小數點後 0 位
primeVueSetOption.plugins.tooltip.callbacks.label = function(context) {
return `${content.y}: ${context.parsed.y}`;
};
break;
}
return [setData, setOption]
return [primeVueSetData, primeVueSetOption]
},
/**
* 建立長條圖
@@ -406,8 +406,8 @@ export default {
let datasets = chartData.data;
let xData;
let yData;
let setData = {};
let setOption = {};
let primeVueSetData = {};
let primeVueSetOption = {};
datasets = datasets.map(value => {
return {
@@ -418,7 +418,7 @@ export default {
xData = datasets.map(i => i.x);
yData = datasets.map(i => i.y)
setData = {
primeVueSetData = {
labels: xData,
datasets: [
{
@@ -428,7 +428,7 @@ export default {
}
]
};
setOption = {
primeVueSetOption = {
responsive: true,
maintainAspectRatio: false,
layout: {
@@ -505,7 +505,7 @@ export default {
},
};
return [setData, setOption]
return [primeVueSetData, primeVueSetOption]
},
/**
* 建立水平長條圖
@@ -518,15 +518,15 @@ export default {
const maxY = chartData.y_axis.max;
const getSimpleTimeLabel = simpleTimeLabel;
const getFollowTimeLabel = followTimeLabel;
let setData = {};
let setOption = {};
let primeVueSetData = {};
let primeVueSetOption = {};
// 大到小排序
chartData.data.sort((a, b) => b.y - a.y);
const xData = chartData.data.map(item => item.x);
const yData = chartData.data.map(item => item.y);
setData = {
primeVueSetData = {
labels: xData,
datasets: [
{
@@ -536,7 +536,7 @@ export default {
}
]
};
setOption = {
primeVueSetOption = {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
@@ -609,33 +609,33 @@ export default {
};
switch (xUnit) {
case 'date':
setOption.plugins.tooltip.callbacks.label = function(context) {
primeVueSetOption.plugins.tooltip.callbacks.label = function(context) {
return `${content.x}: ${getSimpleTimeLabel(context.parsed.x, 2)}`;
};
setOption.scales.x.ticks.callback = function (value, index, ticks) {
primeVueSetOption.scales.x.ticks.callback = function (value, index, ticks) {
return getFollowTimeLabel(value, maxY, 1)
};
break;
case 'count':
setOption.scales.x.ticks.precision = 0; // x 軸顯示小數點後 0 位
setOption.plugins.tooltip.callbacks.label = function(context) {
primeVueSetOption.scales.x.ticks.precision = 0; // x 軸顯示小數點後 0 位
primeVueSetOption.plugins.tooltip.callbacks.label = function(context) {
return `${content.x}: ${context.parsed.x}`;
}
break;
}
if(isSingle) { // 設定一個活動的 y label、提示框文字
setOption.plugins.tooltip.callbacks.title = function(context) {
primeVueSetOption.plugins.tooltip.callbacks.title = function(context) {
return `${content.y}: ${context[0].label}`;
};
setOption.scales.y.ticks.callback = function (value, index, ticks) {
primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) {
let label = xData[index];
return label.length > 21 ? `${label.substring(0, 18)}...` : label
};
}else { // 設定「活動」到「活動」的 y label、提示框文字
setOption.plugins.tooltip.callbacks.title = function(context) {
primeVueSetOption.plugins.tooltip.callbacks.title = function(context) {
return `${content.y}: ${context[0].label.replace(',', ' - ')}`
};
setOption.scales.y.ticks.callback = function (value, index, ticks) {
primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) {
let label = xData[index];
let labelStart = label[0];
let labelEnd = label[1];
@@ -646,7 +646,7 @@ export default {
};
}
return [setData, setOption]
return [primeVueSetData, primeVueSetOption]
},
},
async created() {