feat: Compare bar layout done.
This commit is contained in:
@@ -7,6 +7,7 @@ import Upload from '@/views/Upload/index.vue';
|
|||||||
import Map from '@/views/Discover/Map/index.vue';
|
import Map from '@/views/Discover/Map/index.vue';
|
||||||
import Conformance from '@/views/Discover/Conformance/index.vue';
|
import Conformance from '@/views/Discover/Conformance/index.vue';
|
||||||
import Performance from '@/views/Discover/Performance/index.vue';
|
import Performance from '@/views/Discover/Performance/index.vue';
|
||||||
|
import CompareDashboard from '@/views/Compare/Dashboard/index.vue';
|
||||||
import MemberArea from '@/views/MemberArea/index.vue';
|
import MemberArea from '@/views/MemberArea/index.vue';
|
||||||
import NotFound404 from '@/views/NotFound404.vue';
|
import NotFound404 from '@/views/NotFound404.vue';
|
||||||
|
|
||||||
@@ -93,11 +94,11 @@ const routes = [
|
|||||||
name: "NotFound404",
|
name: "NotFound404",
|
||||||
component: NotFound404,
|
component: NotFound404,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// path: "/discover/performance",
|
path: "/compare/dashboard",
|
||||||
// name: "Performance",
|
name: "CompareDashboard",
|
||||||
// component: Performance,
|
component: CompareDashboard,
|
||||||
// }
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const base_url = import.meta.env.BASE_URL;
|
const base_url = import.meta.env.BASE_URL;
|
||||||
|
|||||||
1622
src/views/Compare/Dashboard/data.js
Normal file
1622
src/views/Compare/Dashboard/data.js
Normal file
File diff suppressed because it is too large
Load Diff
643
src/views/Compare/Dashboard/index.vue
Normal file
643
src/views/Compare/Dashboard/index.vue
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
<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="h2 text-base">Time Usage</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="h2 text-base">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 overflow-x-hidden overflow-y-auto scrollbar scroll-smooth">
|
||||||
|
<section>
|
||||||
|
<p class="h2 px-4 border-b border-neutral-900"><span class="material-symbols-outlined mr-2 align-middle">schedule</span>Time Usage</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<ul class="list-disc list-inside px-4 pl-7">
|
||||||
|
<li id="cycleTime" class="scroll-smooth">
|
||||||
|
<span class="inline-block py-4">Cycle Efficiency</span>
|
||||||
|
<ul>
|
||||||
|
<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>
|
||||||
|
<Chart type="bar" :data="avgCycleEfficiencyData" :options="avgCycleEfficiencyOptions" class="h-96" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li id="processingTime">
|
||||||
|
<span class="inline-block py-4">Processing Time</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">Waiting Time</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>
|
||||||
|
<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>
|
||||||
|
<Chart type="bar" :data="avgWaitingTimeByEdgeData" :options="avgWaitingTimeByEdgeOptions" :style="{ height: avgWaitingTimeByEdgeHeight }" class="h-[500px]" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<p class="h2 px-4 border-b border-neutral-900"><span class="material-symbols-outlined mr-2 align-middle">moving</span>Frequency</p>
|
||||||
|
<ul class="list-disc list-inside px-4 pl-7">
|
||||||
|
<li id="cases">
|
||||||
|
<span class="inline-block py-4">Number of Cases</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>
|
||||||
|
<Chart type="line" :data="freqData" :options="freqOptions" 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.casesByTask.title }}</span>
|
||||||
|
<Chart type="bar" :data="casesByTaskData" :options="casesByTaskOptions" :style="{ height: casesByTaskHeight }" class="h-[500px]"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <StatusBar></StatusBar> -->
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import LoadingStore from '@/stores/loading.js';
|
||||||
|
import PerformanceStore from '@/stores/performance.js';
|
||||||
|
// import StatusBar from '@/components/Discover/StatusBar.vue';
|
||||||
|
import { setLineChartData } from '@/module/setChartData.js';
|
||||||
|
import { simpleTimeLabel, followTimeLabel, dateLabel } from '@/module/timeLabel.js';
|
||||||
|
import { compareDashboardData } from '@/views/Compare/Dashboard/data.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
const loadingStore = LoadingStore();
|
||||||
|
const performanceStore = PerformanceStore();
|
||||||
|
const { isLoading } = storeToRefs(loadingStore);
|
||||||
|
const { performanceData } = storeToRefs(performanceStore);
|
||||||
|
|
||||||
|
return { isLoading, performanceStore, performanceData }
|
||||||
|
},
|
||||||
|
// components: {
|
||||||
|
// StatusBar,
|
||||||
|
// },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
timeUsageData: [
|
||||||
|
{tagId: '#cycleTime', label: 'Cycle Efficiency'},
|
||||||
|
{tagId: '#processingTime', label: 'Processing Time'},
|
||||||
|
{tagId: '#waitingTime', label: 'Waiting Time'},
|
||||||
|
],
|
||||||
|
frequencyData: [
|
||||||
|
{tagId: '#cases', label: 'Number of Cases'},
|
||||||
|
// {tagId: '#trace', label: 'Number of Trace'},
|
||||||
|
// {tagId: '#resource', label: 'Resource'},
|
||||||
|
],
|
||||||
|
contentData: {
|
||||||
|
avgCycleEfficiency: {title: 'Cycle Efficiency', x: 'Date', y: 'Cycle efficiency (%)'},
|
||||||
|
avgProcessTime: {title: 'Average Processing Time', x: 'Date', y: 'Processing time'},
|
||||||
|
avgProcessTimeByTask: {title: 'Average Processing Time by Activity', x: 'Processing time', y: 'Activity'},
|
||||||
|
avgWaitingTime: {title: 'Average Waiting Time', x: 'Date', y: 'Waiting time'},
|
||||||
|
avgWaitingTimeByEdge: {title: 'Average Waiting Time by Activity', x: 'Waiting time', y: 'Activity'},
|
||||||
|
freq: {title: 'New Cases', x: 'Date', y: 'Count'},
|
||||||
|
casesByTask: {title: 'Number of Cases by Activity', x: 'Times', y: 'Activity'},
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
xLabelsData(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;
|
||||||
|
},
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
getLineChart(chartData, content) {
|
||||||
|
let datasetsPrimary = setLineChartData(chartData.data[0].data, chartData.x_axis.max, chartData.x_axis.min, false, chartData.y_axis.max, chartData.y_axis.min);
|
||||||
|
let datasetsSecondary = setLineChartData(chartData.data[1].data, chartData.x_axis.max, chartData.x_axis.min, false, chartData.y_axis.max, chartData.y_axis.min);
|
||||||
|
let minX = chartData.x_axis.min;
|
||||||
|
let maxX = chartData.x_axis.max;
|
||||||
|
let maxY = chartData.y_axis.max;
|
||||||
|
let xData = this.xLabelsData(chartData.x_axis)
|
||||||
|
let labelPrimary = chartData.data[0].label;
|
||||||
|
let labelSecondary = chartData.data[1].label;
|
||||||
|
let setData = {};
|
||||||
|
let setOption = {};
|
||||||
|
const getMoment = (time)=> {
|
||||||
|
return this.$moment(time).format('YYYY/M/D hh:mm:ss')
|
||||||
|
};
|
||||||
|
const getSimpleTimeLabel = simpleTimeLabel;
|
||||||
|
const getFollowTimeLabel = followTimeLabel;
|
||||||
|
|
||||||
|
setData = {
|
||||||
|
labels: xData,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
setOption = {
|
||||||
|
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)}`;
|
||||||
|
},
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'time',
|
||||||
|
min: minX,
|
||||||
|
max: maxX,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: content.x,
|
||||||
|
color: '#334155',
|
||||||
|
font: {
|
||||||
|
size: 12,
|
||||||
|
lineHeight: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
displayFormats: {
|
||||||
|
day: 'yyyy/M/d'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: true,
|
||||||
|
maxRotation: 0, // 不旋轉 lable 0~50
|
||||||
|
color: '#64748b',
|
||||||
|
source: 'labels', // 依比例彈性顯示 label 數量
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
color: '#64748b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true, // scale 包含 0
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: content.y,
|
||||||
|
color: '#334155',
|
||||||
|
font: {
|
||||||
|
size: 12,
|
||||||
|
lineHeight: 2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ticks:{
|
||||||
|
color: '#64748b',
|
||||||
|
padding: 8,
|
||||||
|
callback: function (value, index, ticks) {
|
||||||
|
return getFollowTimeLabel(value, maxY, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: '#64748b',
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
display: false, // 隱藏左側多出來的線
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return [setData, setOption]
|
||||||
|
},
|
||||||
|
getBarChart(chartData, content) {
|
||||||
|
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 getDateLabel = dateLabel;
|
||||||
|
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 setData = {};
|
||||||
|
let setOption = {};
|
||||||
|
|
||||||
|
// 轉為百分比
|
||||||
|
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);
|
||||||
|
|
||||||
|
setData = {
|
||||||
|
labels: xDataPrimary,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: labelPrimary,
|
||||||
|
data: yDataPrimary,
|
||||||
|
backgroundColor: this.colorPrimary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: labelSecondary,
|
||||||
|
data: yDataSecondary,
|
||||||
|
backgroundColor: this.colorSecondary,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
setOption = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
top: 16,
|
||||||
|
left: 8,
|
||||||
|
right: 8,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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: {
|
||||||
|
x: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: content.x,
|
||||||
|
color: '#334155',
|
||||||
|
font: {
|
||||||
|
size: 12,
|
||||||
|
lineHeight: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: true,
|
||||||
|
color: '#64748b',
|
||||||
|
callback: function(v, i, t) {
|
||||||
|
let label = xDataPrimary[i]
|
||||||
|
return getDateLabel(label, maxX, minX)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
color: '#64748b',
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true, // scale 包含 0
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: content.y,
|
||||||
|
color: '#334155',
|
||||||
|
font: {
|
||||||
|
size: 12,
|
||||||
|
lineHeight: 2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ticks:{
|
||||||
|
color: '#64748b',
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: '#64748b',
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
display: false, // 隱藏左側多出來的線
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return [setData, setOption]
|
||||||
|
},
|
||||||
|
getHorizontalBarChart(chartData, content, isSingle) {
|
||||||
|
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 setData = {};
|
||||||
|
let setOption = {};
|
||||||
|
|
||||||
|
// 大到小排序: 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 xData = datasetsPrimary.map(item => item.x);
|
||||||
|
const yDataPrimary = datasetsPrimary.map(item => item.y);
|
||||||
|
const yDataSecondary = datasetsSecondary.map(item => item.y);
|
||||||
|
|
||||||
|
setData = {
|
||||||
|
labels: xData,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: labelPrimary,
|
||||||
|
data: yDataPrimary,
|
||||||
|
backgroundColor: this.colorPrimary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: labelSecondary,
|
||||||
|
data: yDataSecondary,
|
||||||
|
backgroundColor: this.colorSecondary,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
setOption = {
|
||||||
|
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: {
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: content.x,
|
||||||
|
color: '#334155',
|
||||||
|
font: {
|
||||||
|
size: 12,
|
||||||
|
lineHeight: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: true,
|
||||||
|
maxRotation: 0, // 不旋轉 lable 0~50
|
||||||
|
color: '#64748b',
|
||||||
|
callback: function (value, index, ticks) {
|
||||||
|
return getFollowTimeLabel(value, maxY, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: '#64748b',
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
display:false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true, // scale 包含 0
|
||||||
|
type: 'category',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: content.y,
|
||||||
|
color: '#334155',
|
||||||
|
font: {
|
||||||
|
size: 12,
|
||||||
|
lineHeight: 2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ticks:{
|
||||||
|
color: '#64748b',
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display:false,
|
||||||
|
color: '#64748b',
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
display: false, // 隱藏左側多出來的線
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if(isSingle) { // 設定一個活動的 y label、提示框文字
|
||||||
|
setOption.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];
|
||||||
|
return label.length > 21 ? `${label.substring(0, 18)}...` : label
|
||||||
|
};
|
||||||
|
}else { // 設定「活動」到「活動」的 y label、提示框文字
|
||||||
|
setOption.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];
|
||||||
|
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 [setData, setOption]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
this.isLoading = true; // moubeted 才停止 loading
|
||||||
|
// log: 695977238 ; filter: 746150051
|
||||||
|
// [{"log_id":695977238},{"filter_id":746150051}]
|
||||||
|
// 要判斷資料有無 null,問貓貓是哪個階層 null?
|
||||||
|
// Activity 會有 0, null 的組合嗎?
|
||||||
|
|
||||||
|
// const routeParams = this.$route.params;
|
||||||
|
// console.log(routeParams);
|
||||||
|
// let id = routeParams.fileId;
|
||||||
|
// let type = routeParams.type;
|
||||||
|
|
||||||
|
// // 取得 Performance Data
|
||||||
|
// await this.performanceStore.getPerformance(type, id);
|
||||||
|
this.avgProcessTimeByTaskHeight = await this.getHorizontalBarHeight(compareDashboardData.time.avg_process_time_by_task);
|
||||||
|
this.avgWaitingTimeByEdgeHeight = await this.getHorizontalBarHeight(compareDashboardData.time.avg_waiting_time_by_edge);
|
||||||
|
this.casesByTaskHeight = await this.getHorizontalBarHeight(compareDashboardData.freq.cases_by_task);
|
||||||
|
// create chart
|
||||||
|
[this.avgCycleEfficiencyData, this.avgCycleEfficiencyOptions] = this.getBarChart(compareDashboardData.time.avg_cycle_efficiency, this.contentData.avgCycleEfficiency);
|
||||||
|
[this.avgProcessTimeData, this.avgProcessTimeOptions] = this.getLineChart(compareDashboardData.time.avg_process_time, this.contentData.avgProcessTime);
|
||||||
|
[this.avgProcessTimeByTaskData, this.avgProcessTimeByTaskOptions] = this.getHorizontalBarChart(compareDashboardData.time.avg_process_time_by_task, this.contentData.avgProcessTimeByTask, true);
|
||||||
|
[this.avgWaitingTimeData, this.avgWaitingTimeOptions] = this.getLineChart(compareDashboardData.time.avg_waiting_time, this.contentData.avgWaitingTime);
|
||||||
|
[this.avgWaitingTimeByEdgeData, this.avgWaitingTimeByEdgeOptions] = this.getHorizontalBarChart(compareDashboardData.time.avg_waiting_time_by_edge, this.contentData.avgWaitingTimeByEdge, false);
|
||||||
|
[this.freqData, this.freqOptions] = this.getLineChart(compareDashboardData.freq.cases, this.contentData.freq);
|
||||||
|
[this.casesByTaskData, this.casesByTaskOptions] = this.getHorizontalBarChart(compareDashboardData.freq.cases_by_task, this.contentData.casesByTask, true);
|
||||||
|
// 停止 loading
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.active {
|
||||||
|
@apply text-primary
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -542,7 +542,6 @@ export default {
|
|||||||
async created() {
|
async created() {
|
||||||
this.isLoading = true; // moubeted 才停止 loading
|
this.isLoading = true; // moubeted 才停止 loading
|
||||||
const routeParams = this.$route.params;
|
const routeParams = this.$route.params;
|
||||||
console.log(routeParams);
|
|
||||||
let id = routeParams.fileId;
|
let id = routeParams.fileId;
|
||||||
let type = routeParams.type;
|
let type = routeParams.type;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user