1318 lines
49 KiB
Vue
1318 lines
49 KiB
Vue
<template>
|
||
<main class="h-screen-main relative">
|
||
<div class="h-full relative bg-neutral-50">
|
||
<div class="flex justify-start items-start p-4 h-full">
|
||
<!-- tag -->
|
||
<aside class="border-r border-neutral-300 pr-4 mr-4 h-full sticky top-0 self-start">
|
||
<section class="px-2 space-y-2 w-56 h-full">
|
||
<div>
|
||
<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">
|
||
{{ item.label }}
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<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>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
</aside>
|
||
<!-- graph -->
|
||
<article class="w-full h-full pr-2 mr-2 overflow-x-hidden overflow-y-auto scrollbar scroll-smooth">
|
||
<section>
|
||
<p class="h1 px-4 border-b border-neutral-900"><span class="material-symbols-outlined mr-2 align-middle">
|
||
{{ i18next.t("Compare.schedule") }}
|
||
</span>
|
||
{{ i18next.t("Compare.timeUsage") }}
|
||
</p>
|
||
<ul class="list-disc list-inside px-4 pl-7 text-sm">
|
||
<li id="cycleTime" class="scroll-smooth">
|
||
<span class="inline-block py-4">{{ i18next.t("Compare.cycleEfficiency") }}</span>
|
||
<ul>
|
||
<li class="bg-neutral-10 mb-4 p-1 border border-neutral-300 rounded">
|
||
<span class="block font-bold text-sm leading-loose text-center my-2">
|
||
{{ contentData.avgCycleTime.title }}
|
||
</span>
|
||
<Chart type="line" :data="avgCycleTimeData" :options="avgCycleTimeOptions" class="h-96" />
|
||
</li>
|
||
<li class="bg-neutral-10 p-1 border border-neutral-300 rounded">
|
||
<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>
|
||
<Chart type="bar" :data="avgCycleEfficiencyData" :options="avgCycleEfficiencyOptions" class="h-96" />
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li id="processingTime">
|
||
<span class="inline-block py-4 text-sm">{{ i18next.t("Compare.ProcessingTime") }}</span>
|
||
<ul>
|
||
<li class="bg-neutral-10 mb-4 p-1 border border-neutral-300 rounded">
|
||
<span class="block font-bold text-sm leading-loose text-center my-2">
|
||
{{ contentData.avgProcessTime.title }}
|
||
</span>
|
||
<Chart type="line" :data="avgProcessTimeData" :options="avgProcessTimeOptions" class="h-96" />
|
||
</li>
|
||
<li class="bg-neutral-10 p-1 border border-neutral-300 rounded">
|
||
<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 }"/>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li id="waitingTime">
|
||
<span class="inline-block py-4 text-sm">{{ i18next.t("Compare.WaitingTime") }}</span>
|
||
<ul>
|
||
<li class="bg-neutral-10 mb-4 p-1 border border-neutral-300 rounded">
|
||
<span class="block font-bold text-sm leading-loose text-center my-2">
|
||
{{ contentData.avgWaitingTime.title }}
|
||
<span class="material-symbols-outlined align-middle ml-2 cursor-pointer !text-base"
|
||
v-tooltip.bottom="tooltip.avgWaitingTime">
|
||
info
|
||
</span></span>
|
||
<Chart type="line" :data="avgWaitingTimeData" :options="avgWaitingTimeOptions" class="h-96" />
|
||
</li>
|
||
<li class="bg-neutral-10 p-1 border border-neutral-300 rounded">
|
||
<span class="block font-bold text-sm leading-loose text-center my-2">
|
||
{{ contentData.avgWaitingTimeByEdge.title }}
|
||
<span class="material-symbols-outlined align-middle ml-2 cursor-pointer !text-base"
|
||
v-tooltip.bottom="tooltip.avgWaitingTimeByEdge">{{ i18next.t("Compare.info") }}</span></span>
|
||
<div>
|
||
<Chart v-if="avgWaitingTimeByEdgeData !== null" type="bar" :data="avgWaitingTimeByEdgeData"
|
||
:options="avgWaitingTimeByEdgeOptions" :style="{ height: avgWaitingTimeByEdgeHeight }" class="h-[500px]" />
|
||
<div v-else class="h-96 bg-neutral-100 m-4 flex">
|
||
<p class="h2 text-danger m-auto">{{ i18next.t("Compare.NoWaitingTime") }}</p>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<p class="h1 px-4 border-b border-neutral-900 mt-2"><span class="material-symbols-outlined mr-2 align-middle">
|
||
{{i18next.t("Compare.moving")}}
|
||
</span>
|
||
{{ i18next.t("Compare.frequency") }}
|
||
</p>
|
||
<ul class="list-disc list-inside px-4 pl-7 text-sm">
|
||
<li id="cases">
|
||
<span class="inline-block py-4">{{ i18next.t("Compare.NumberOfCases") }}</span>
|
||
<ul>
|
||
<li class="bg-neutral-10 mb-4 p-1 border border-neutral-300 rounded">
|
||
<span class="block font-bold text-sm leading-loose text-center my-2">{{ contentData.freq.title }}</span>
|
||
<!-- UNUSED <Chart type="line" :data="freqData" :options="freqOptions" class="h-96" /> -->
|
||
<FreqChart v-if="compareDashboardData" :chartData="compareDashboardData?.freq?.cases" :content="contentData?.freq" yUnit="count"
|
||
pageName='Compare'
|
||
/>
|
||
</li>
|
||
<li class="bg-neutral-10 p-1 border border-neutral-300 rounded">
|
||
<span class="block font-bold text-sm leading-loose text-center my-2">
|
||
{{ contentData.casesByTask.title }}
|
||
</span>
|
||
<Chart type="bar" :data="casesByTaskData" :options="casesByTaskOptions" :style="{ height: casesByTaskHeight }"
|
||
class="h-[500px]"/>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
</article>
|
||
<!-- Sidebar: State -->
|
||
<div class="bg-transparent z-10">
|
||
<ul class="flex flex-col justify-center items-center">
|
||
<li class="inline-flex items-center justify-center border border-neutral-500 rounded-full w-9 h-9
|
||
cursor-pointer bg-neutral-50 drop-shadow hover:border-primary" @click="sidebarState = !sidebarState"
|
||
:class="{'border-primary': sidebarState}" id="compareState">
|
||
<span class="material-symbols-outlined !text-2xl text-neutral-500 hover:text-primary p-1.5"
|
||
:class="[sidebarState ? 'text-primary' : 'text-neutral-500']">
|
||
info
|
||
</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<SidebarStates v-model:visible="sidebarState"></SidebarStates>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</template>
|
||
<script>
|
||
import { storeToRefs } from 'pinia';
|
||
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, getDateLabelByMinMaxDate,
|
||
setTimeStringFormatBaseOnTimeDifference,
|
||
mapTimestampToAxisTicksByFormat,
|
||
getStepSizeOfYTicks,
|
||
getYTicksByIndex,
|
||
} from '@/module/timeLabel.js';
|
||
import i18next from '@/i18n/i18n';
|
||
import {
|
||
knownLayoutChartOption,
|
||
knownScaleLineChartOptions,
|
||
knownScaleHorizontalChartOptions,
|
||
knownScaleBarChartOptions,
|
||
} from "@/constants/constants.js";
|
||
import FreqChart from '../../Discover/Performance/FreqChart.vue';
|
||
|
||
|
||
export default {
|
||
setup() {
|
||
const loadingStore = LoadingStore();
|
||
const compareStore = CompareStore();
|
||
const { isLoading } = storeToRefs(loadingStore);
|
||
const { compareDashboardData } = storeToRefs(compareStore);
|
||
|
||
return { isLoading, compareStore, compareDashboardData }
|
||
},
|
||
components: {
|
||
SidebarStates,
|
||
FreqChart,
|
||
},
|
||
data() {
|
||
return {
|
||
i18next: i18next,
|
||
timeUsageData: [
|
||
{tagId: '#cycleTime', label: i18next.t("Compare.cycleEfficiency")},
|
||
{tagId: '#processingTime', label: i18next.t("Compare.ProcessingTime")},
|
||
{tagId: '#waitingTime', label: i18next.t("Compare.WaitingTime")},
|
||
],
|
||
frequencyData: [
|
||
{tagId: '#cases', label: i18next.t("Compare.NumberOfCases")},
|
||
// {tagId: '#trace', label: 'Number of Trace'},
|
||
// {tagId: '#resource', label: 'Resource'},
|
||
],
|
||
contentData: {
|
||
avgCycleTime: {title: i18next.t("Compare.avgCycleTimeTitle"), x: i18next.t("Compare.labelDateX"),
|
||
y: i18next.t("Compare.avgCycleTimeY")},
|
||
avgCycleEfficiency: {title: i18next.t("Compare.avgCycleEfficiencyTitle"), x: i18next.t("Compare.labelDateX"),
|
||
y: i18next.t("Compare.avgCycleEfficiencyY")},
|
||
avgProcessTime: {title: i18next.t("Compare.avgProcessTimeTitle"), x: i18next.t("Compare.labelDateX"),
|
||
y: i18next.t("Compare.avgProcessTimeY")},
|
||
avgProcessTimeByTask: {title: i18next.t("Compare.avgProcessTimeByTaskTitle"),
|
||
x: i18next.t("Compare.avgProcessTimeByTaskX"), y: i18next.t("Compare.labelActivityY")},
|
||
avgWaitingTime: {title: i18next.t("Compare.avgWaitingTimeTitle"), x: i18next.t("Compare.labelDateX"),
|
||
y: i18next.t("Compare.avgWaitingTimeY")},
|
||
avgWaitingTimeByEdge: {title: i18next.t("Compare.avgWaitingTimeByEdgeTitle"),
|
||
x: i18next.t("Compare.avgWaitingTimeByEdgeX"),
|
||
y: i18next.t("Compare.avgWaitingTimeByEdgeY")},
|
||
freq: {title: i18next.t("Compare.freqTitle"), x: i18next.t("Compare.labelDateX"), y: i18next.t("Compare.freqY")},
|
||
casesByTask: {title: i18next.t("Compare.casesByTaskTitle"), x: i18next.t("Compare.casesByTaskX"),
|
||
y: i18next.t("Compare.labelActivityY")},
|
||
},
|
||
tooltip: {
|
||
avgCycleEfficiency: {
|
||
value: i18next.t("Compare.avgCycleEfficiency"),
|
||
class: '!max-w-[212px] !text-[10px] !opacity-80',
|
||
autoHide: false,
|
||
},
|
||
avgWaitingTime: {
|
||
value: i18next.t("Compare.avgWaitingTime"),
|
||
class: '!max-w-[212px] !text-[10px] !opacity-80',
|
||
autoHide: false,
|
||
},
|
||
avgWaitingTimeByEdge: {
|
||
value: i18next.t("Compare.avgWaitingTimeByEdge"),
|
||
class: '!max-w-[212px] !text-[10px] !opacity-80',
|
||
autoHide: false,
|
||
},
|
||
},
|
||
isActive: null,
|
||
avgCycleTimeData: null,
|
||
avgCycleTimeOptions: null,
|
||
avgCycleEfficiencyData: null,
|
||
avgCycleEfficiencyOptions: null,
|
||
avgProcessTimeData: null,
|
||
avgProcessTimeOptions: null,
|
||
avgProcessTimeByTaskData: null,
|
||
avgProcessTimeByTaskOptions: null,
|
||
avgWaitingTimeData: null,
|
||
avgWaitingTimeOptions: null,
|
||
avgWaitingTimeByEdgeData: null,
|
||
avgWaitingTimeByEdgeOptions: null,
|
||
freqData: null,
|
||
freqOptions: null,
|
||
casesByTaskData: null,
|
||
casesByTaskOptions: null,
|
||
horizontalBarHeight: 500, // horizontal Bar default height
|
||
avgProcessTimeByTaskHeight: 500,
|
||
avgWaitingTimeByEdgeHeight: 500,
|
||
casesByTaskHeight: 500,
|
||
colorPrimary: '#0099FF',
|
||
colorSecondary: '#FFAA44',
|
||
sidebarState: false, // SideBar: Summary
|
||
}
|
||
},
|
||
methods: {
|
||
/**
|
||
* 手刻折線圖 x label 時間刻度
|
||
* @param { object } valueData {min: '2022-02-20T19:54:12', max: '2023-11-27T07:21:53'}
|
||
*/
|
||
setXLabelsData(valueData) {
|
||
let min = new Date(valueData.min).getTime();
|
||
let max = new Date(valueData.max).getTime();
|
||
let numPoints = 12;
|
||
let step = (max - min) / (numPoints - 1);
|
||
let data = [];
|
||
for(let i = 0; i< numPoints; i++) {
|
||
const x = min + i * step;
|
||
data.push(x);
|
||
}
|
||
return data;
|
||
},
|
||
/**
|
||
* 讓長條圖依 data 數量增加高度
|
||
* @param { object } chartData chart data
|
||
*/
|
||
getHorizontalBarHeight(chartData) {
|
||
const totalBars = chartData.x_axis.labels.length;
|
||
let horizontalBar = this.horizontalBarHeight;
|
||
|
||
if(totalBars > 10){
|
||
horizontalBar = (totalBars - 10) * 16 * 2 + this.horizontalBarHeight
|
||
};
|
||
|
||
return horizontalBar + 'px'
|
||
},
|
||
/**
|
||
* 建立折線圖
|
||
* @param { object } chartData chart data
|
||
* @param { object } content titels 標題文字
|
||
* @param { string } yUnit y 軸單位 'date' | 'count',可傳入以上任一。
|
||
* @returns { [primeVueSetData, primeVueSetOption] } 這兩者為符合 primeVue 圖表格式的數據資料
|
||
*/
|
||
getLineChart(chartData, content, yUnit) {
|
||
let datasetsPrimary;
|
||
let datasetsSecondary;
|
||
let minX = chartData.x_axis.min;
|
||
let maxX = chartData.x_axis.max;
|
||
let maxY = chartData.y_axis.max;
|
||
let xLabelData;
|
||
let labelPrimary = chartData.data[0].label;
|
||
let labelSecondary = chartData.data[1].label;
|
||
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);
|
||
xLabelData = this.setXLabelsData(chartData.x_axis);
|
||
break;
|
||
case 'count': // 次數 10 個點
|
||
datasetsPrimary = chartData.data[0].data;
|
||
datasetsSecondary = chartData.data[1].data;
|
||
xLabelData = chartData.data[0].data.map(item => new Date(item.x).getTime());
|
||
break;
|
||
}
|
||
|
||
// 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(xLabelData, formatToSet);
|
||
const customizedScaleOption = this.getCustomizedScaleOption(
|
||
knownScaleLineChartOptions, {
|
||
customizeOptions: {
|
||
content, ticksOfXAxis,
|
||
}
|
||
});
|
||
|
||
primeVueSetData = {
|
||
labels: xLabelData,
|
||
datasets: [
|
||
{
|
||
label: labelPrimary,
|
||
data: datasetsPrimary,
|
||
fill: false,
|
||
tension: 0, // 貝茲曲線張力
|
||
borderColor: this.colorPrimary,
|
||
pointBackgroundColor: this.colorPrimary,
|
||
},
|
||
{
|
||
label: labelSecondary,
|
||
data: datasetsSecondary,
|
||
fill: false,
|
||
tension: 0, // 貝茲曲線張力
|
||
borderColor: this.colorSecondary,
|
||
pointBackgroundColor: this.colorSecondary,
|
||
}
|
||
]
|
||
};
|
||
primeVueSetOption = {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
layout: {
|
||
padding: {
|
||
top: 16,
|
||
left: 8,
|
||
right: 8,
|
||
}
|
||
},
|
||
plugins: {
|
||
legend: false, // 圖例
|
||
tooltip: {
|
||
// displayColors: false,
|
||
mode: 'index',
|
||
titleFont: {weight: 'normal'},
|
||
callbacks: {
|
||
title: function(context) {
|
||
return `${content.x}: ${getMoment(context[0].parsed.x)}`;
|
||
},
|
||
},
|
||
},
|
||
title: {
|
||
display: false,
|
||
},
|
||
},
|
||
scales: customizedScaleOption,
|
||
};
|
||
switch (yUnit) {
|
||
case 'date':
|
||
primeVueSetOption.plugins.tooltip.callbacks.label = function(context) {
|
||
let value = getSimpleTimeLabel(context.parsed.y, 2);
|
||
|
||
switch (context.datasetIndex) {
|
||
case 0: // Primary
|
||
return `${labelPrimary}: ${value}`;
|
||
case 1: // Secondary
|
||
return `${labelSecondary}: ${value}`;
|
||
}
|
||
};
|
||
primeVueSetOption.scales.x.min = minX;
|
||
primeVueSetOption.scales.x.max = maxX;
|
||
primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) {
|
||
const {resultStepSize: YTickStepSize, unitToUse} = getStepSizeOfYTicks(maxY, ticks.length); // Stepsize only needs to be calculated once
|
||
return getYTicksByIndex(YTickStepSize, index, unitToUse);
|
||
}
|
||
break;
|
||
case 'count':
|
||
primeVueSetOption.scales.y.ticks.precision = 0; // y 軸顯示小數點後 0 位
|
||
primeVueSetOption.plugins.tooltip.callbacks.label = function(context) {
|
||
let value = context.parsed.y;
|
||
|
||
switch (context.datasetIndex) {
|
||
case 0: // Primary
|
||
return `${labelPrimary}: ${value}`;
|
||
case 1: // Secondary
|
||
return `${labelSecondary}: ${value}`;
|
||
}
|
||
};
|
||
break;
|
||
}
|
||
|
||
// JSON.parse(JSON.stringify()) wouldn't work
|
||
return [primeVueSetData, primeVueSetOption];
|
||
},
|
||
/**
|
||
* 建立長條圖
|
||
* @param { object } chartData chart data
|
||
* @param { object } content titles 文字標題
|
||
*/
|
||
getBarChart(chartData, content, caller) {
|
||
const maxX = chartData.x_axis.max;
|
||
const minX = chartData.x_axis.min;
|
||
const getMoment = (time)=> this.$moment(time).format('YYYY/MM/DD');
|
||
const getDateLabel = getDateLabelByMinMaxDate;
|
||
let datasetsPrimary = chartData.data[0].data;
|
||
let xDataPrimary;
|
||
let yDataPrimary;
|
||
let labelPrimary = chartData.data[0].label;
|
||
let datasetsSecondary = chartData.data[1].data;
|
||
let xDataSecondary;
|
||
let yDataSecondary;
|
||
let labelSecondary = chartData.data[1].label;
|
||
let primeVueSetData = {};
|
||
let primeVueSetOption = {};
|
||
|
||
// 轉為百分比
|
||
datasetsPrimary = datasetsPrimary.map(value => {
|
||
return {
|
||
x: getMoment(value.x),
|
||
y: value.y === null ? null : value.y * 100
|
||
}
|
||
});
|
||
xDataPrimary = datasetsPrimary.map(i => i.x);
|
||
yDataPrimary = datasetsPrimary.map(i => i.y);
|
||
datasetsSecondary = datasetsSecondary.map(value => {
|
||
return {
|
||
x: getMoment(value.x),
|
||
y: value.y === null ? null : value.y * 100
|
||
}
|
||
});
|
||
xDataSecondary = datasetsSecondary.map(i => i.x);
|
||
yDataSecondary = datasetsSecondary.map(i => i.y);
|
||
|
||
primeVueSetData = {
|
||
labels: xDataPrimary,
|
||
datasets: [
|
||
{
|
||
label: labelPrimary,
|
||
data: yDataPrimary,
|
||
backgroundColor: this.colorPrimary,
|
||
},
|
||
{
|
||
label: labelSecondary,
|
||
data: yDataSecondary,
|
||
backgroundColor: this.colorSecondary,
|
||
},
|
||
]
|
||
};
|
||
primeVueSetOption = {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
layout: knownLayoutChartOption,
|
||
interaction: {
|
||
intersect: false,
|
||
mode: 'index', // 可顯示全部的 data label
|
||
},
|
||
plugins: {
|
||
legend: false, // 圖例
|
||
tooltip: {
|
||
// displayColors: false,
|
||
titleFont: {weight: 'normal'},
|
||
callbacks: {
|
||
title: function(context) {
|
||
return `${content.x}: ${context[0].label}`;
|
||
},
|
||
label: function(context) {
|
||
let value = context.parsed.y;
|
||
|
||
value = context.parsed.y === 0 ? 0 :
|
||
context.parsed.y === null ? "n/a" :
|
||
context.parsed.y.toFixed(2);
|
||
switch (context.datasetIndex) {
|
||
case 0: // Primary
|
||
return `${labelPrimary}: ${value}`;
|
||
case 1: // Secondary
|
||
return `${labelSecondary}: ${value}`;
|
||
}
|
||
}
|
||
},
|
||
},
|
||
title: {
|
||
display: false,
|
||
},
|
||
},
|
||
scales: this.customizeScaleChartOptionTitleByContent(knownScaleBarChartOptions, content),
|
||
};
|
||
|
||
if(caller === "Cycle Eff") { //針對 cycle efficiency 特別處理
|
||
primeVueSetOption.scales.y.reverse = true; //不明原因上下顛倒,所以要顛倒過來
|
||
primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) {
|
||
return 10 * index; //百分比
|
||
}
|
||
}
|
||
|
||
return [primeVueSetData, primeVueSetOption]
|
||
},
|
||
/**
|
||
* 建立水平長條圖
|
||
* @param { object } chartData chart data
|
||
* @param { object } content titels 標題的文字
|
||
* @param { boolean } isSingle 單個或雙數 activity
|
||
* @param { string } xUnit x 軸單位 'date' | 'count',可傳入以上任一。
|
||
*/
|
||
getHorizontalBarChart(chartData, content, isSingle, xUnit) {
|
||
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: this.customizeScaleChartOptionTitleByContent(knownScaleHorizontalChartOptions, null),
|
||
};
|
||
switch (xUnit) {
|
||
case 'date':
|
||
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;
|
||
case 'count':
|
||
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]
|
||
},
|
||
/**
|
||
*
|
||
* @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
|
||
* @param content whose property includes x and y and stand for titles
|
||
*
|
||
* @returns { object } an object modified with two titles
|
||
*/
|
||
customizeScaleChartOptionTitleByContent(whichScaleObj, content){
|
||
if (!content) {
|
||
// Early return
|
||
return whichScaleObj;
|
||
}
|
||
|
||
return {
|
||
...whichScaleObj,
|
||
x: {
|
||
...whichScaleObj.x,
|
||
title: {
|
||
...whichScaleObj.x.title,
|
||
text: content.x
|
||
}
|
||
},
|
||
y: {
|
||
...whichScaleObj.y,
|
||
title: {
|
||
...whichScaleObj.y.title,
|
||
text: content.y
|
||
}
|
||
}
|
||
};
|
||
},
|
||
/**
|
||
*
|
||
* @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) {
|
||
// 根據不同的級距客製化 x 軸的時間刻度
|
||
return ticksOfXAxis[index];
|
||
},
|
||
},
|
||
},
|
||
};
|
||
},
|
||
/**
|
||
* 建立Compare頁面的折線圖,並且避免同一個畫面中的設定值彼此覆蓋
|
||
* @param { object } chartData chart data
|
||
* @param { object } content titels 標題文字
|
||
* @param { string } yUnit y 軸單位 'date' | 'count',可傳入以上任一。
|
||
* @returns { [primeVueSetData, primeVueSetOption] } 這兩者為符合 primeVue 圖表格式的數據資料
|
||
*/
|
||
getLineChart(chartData, content, yUnit) {
|
||
let datasetsPrimary;
|
||
let datasetsSecondary;
|
||
let minX = chartData.x_axis.min;
|
||
let maxX = chartData.x_axis.max;
|
||
let maxY = chartData.y_axis.max;
|
||
let xLabelData;
|
||
let labelPrimary = chartData.data[0].label;
|
||
let labelSecondary = chartData.data[1].label;
|
||
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);
|
||
xLabelData = this.setXLabelsData(chartData.x_axis);
|
||
break;
|
||
case 'count': // 次數 10 個點
|
||
datasetsPrimary = chartData.data[0].data;
|
||
datasetsSecondary = chartData.data[1].data;
|
||
xLabelData = chartData.data[0].data.map(item => new Date(item.x).getTime());
|
||
break;
|
||
}
|
||
|
||
// 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(xLabelData, formatToSet);
|
||
|
||
primeVueSetData = {
|
||
labels: xLabelData,
|
||
datasets: [
|
||
{
|
||
label: labelPrimary,
|
||
data: datasetsPrimary,
|
||
fill: false,
|
||
tension: 0, // 貝茲曲線張力
|
||
borderColor: this.colorPrimary,
|
||
pointBackgroundColor: this.colorPrimary,
|
||
},
|
||
{
|
||
label: labelSecondary,
|
||
data: datasetsSecondary,
|
||
fill: false,
|
||
tension: 0, // 貝茲曲線張力
|
||
borderColor: this.colorSecondary,
|
||
pointBackgroundColor: this.colorSecondary,
|
||
}
|
||
]
|
||
};
|
||
primeVueSetOption = {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
layout: {
|
||
padding: {
|
||
top: 16,
|
||
left: 8,
|
||
right: 8,
|
||
}
|
||
},
|
||
plugins: {
|
||
legend: false, // 圖例
|
||
tooltip: {
|
||
// displayColors: false,
|
||
mode: 'index',
|
||
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: {
|
||
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];
|
||
},
|
||
/**
|
||
* 建立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
|
||
|
||
const routeParams = this.$route.params;
|
||
const primaryType = routeParams.primaryType;
|
||
const secondaryType = routeParams.secondaryType;
|
||
const primaryId = routeParams.primaryId;
|
||
const secondaryId = routeParams.secondaryId;
|
||
const typeMap = {
|
||
'log': 'log_id',
|
||
'filter': 'filter_id'
|
||
};
|
||
const primaryTypeParam = typeMap[primaryType];
|
||
const secondaryTypeParam = typeMap[secondaryType];
|
||
const queryParams = [
|
||
{ [primaryTypeParam]: primaryId },
|
||
{ [secondaryTypeParam]: secondaryId }
|
||
];
|
||
|
||
// 取得 Compare Data
|
||
await this.compareStore.getCompare(queryParams);
|
||
this.avgProcessTimeByTaskHeight = await this.getHorizontalBarHeight(this.compareDashboardData.time.avg_process_time_by_task);
|
||
if(this.compareDashboardData.time.avg_waiting_time_by_edge !== null) {
|
||
this.avgWaitingTimeByEdgeHeight = await this.getHorizontalBarHeight(this.compareDashboardData.time.avg_waiting_time_by_edge);
|
||
};
|
||
this.casesByTaskHeight = await this.getHorizontalBarHeight(this.compareDashboardData.freq.cases_by_task);
|
||
// create chart
|
||
[this.avgCycleTimeData, this.avgCycleTimeOptions] = this.getLineChart(
|
||
this.compareDashboardData.time.avg_cycle_time, this.contentData.avgCycleTime, 'date');
|
||
[this.avgCycleEfficiencyData, this.avgCycleEfficiencyOptions] = this.getBarChart(
|
||
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.getAvgProcessTimeHorizontalBarChart(
|
||
this.compareDashboardData.time.avg_process_time_by_task,
|
||
this.contentData.avgProcessTimeByTask, true, 'date');
|
||
[this.avgWaitingTimeData, this.avgWaitingTimeOptions] = this.getLineChart(
|
||
this.compareDashboardData.time.avg_waiting_time, this.contentData.avgWaitingTime, 'date');
|
||
if(this.compareDashboardData.time.avg_waiting_time_by_edge !== null) {
|
||
[this.avgWaitingTimeByEdgeData, this.avgWaitingTimeByEdgeOptions] = this.getHorizontalBarChart(
|
||
this.compareDashboardData.time.avg_waiting_time_by_edge,
|
||
this.contentData.avgWaitingTimeByEdge, false, 'date');
|
||
} else {
|
||
[this.avgWaitingTimeByEdgeData, this.avgWaitingTimeByEdgeOptions] = [null, null]
|
||
}
|
||
[this.freqData, this.freqOptions] = this.getLineChart(
|
||
this.compareDashboardData.freq.cases, this.contentData.freq, 'count');
|
||
[this.casesByTaskData, this.casesByTaskOptions] = this.getCaseByTaskHorizontalBarChart(
|
||
this.compareDashboardData.freq.cases_by_task, this.contentData.casesByTask, true, 'count');
|
||
},
|
||
mounted() {
|
||
// 停止 loading
|
||
this.isLoading = false;
|
||
}
|
||
}
|
||
</script>
|
||
<style scoped>
|
||
.active {
|
||
@apply text-primary
|
||
}
|
||
</style>
|