Files
lucia-frontend/src/components/Discover/Conformance/ConformanceResults.vue
2026-03-09 13:56:47 +08:00

987 lines
30 KiB
Vue

<template>
<section
class="p-4 mr-0.5 space-y-2 h-full w-[calc(100vw_-_316px)] overflow-y-auto scrollbar float-right"
>
<div
v-show="isCoverPlate"
class="w-[calc(100vw_-_300px)] h-screen-main fixed bottom-0 right-0 bg-gradient-to-tr from-neutral-500/50 to-neutral-900/50 z-[1]"
></div>
<!-- title -->
<p class="text-base leading-10 font-bold">
Conformance Checking Results ({{ data.total }})
</p>
<!-- total group -->
<ul class="text-neutral-10 text-sm flex gap-2 py-2">
<li class="bg-cfm-primary rounded-full px-4 py-1 space-x-2">
<span class="material-symbols-outlined !text-base align-middle mr-2"
>check_circle</span
>Conforming<span>{{ data.counts.conforming }}</span>
</li>
<li class="bg-cfm-secondary rounded-full px-4 py-1 space-x-2">
<span class="material-symbols-outlined !text-base align-middle mr-2"
>cancel</span
>Not Conforming<span>{{ data.counts.not_conforming }}</span>
</li>
<li
class="bg-neutral-700 rounded-full px-4 py-1 space-x-2"
v-show="data.counts.not_applicable != 0"
>
<iconNA class="inline-block mr-1"></iconNA>Not Applicable<span>{{
data.counts.not_applicable
}}</span>
</li>
</ul>
<!-- chart -->
<div class="flex gap-4">
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-1/2">
<div class="p-2 flex justify-between items-center">
<div>
<span class="block text-sm font-bold mb-2"
>Conformance Rate<span
class="material-symbols-outlined !text-sm align-middle ml-2 cursor-pointer"
v-tooltip.bottom="tooltip.rate"
>info</span
></span
>
<small class="text-neutral-700 font-normal block"
>{{ data.charts.rate.xMin }} ~ {{ data.charts.rate.xMax }}</small
>
</div>
<span class="text-2xl font-bold">{{ data.charts.rate.rate }}%</span>
</div>
<Chart
type="line"
:data="rateChartData"
:options="rateChartOptions"
class="w-[99%]"
/>
</div>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-1/2">
<div class="p-2 flex justify-between items-center">
<div>
<span class="block text-sm font-bold mb-2"
>Cases<span
class="material-symbols-outlined !text-sm align-middle ml-2 cursor-pointer"
v-tooltip.bottom="tooltip.case"
>info</span
></span
>
<small class="text-neutral-700 font-normal block"
>{{ data.charts.cases.xMin }} ~
{{ data.charts.cases.xMax }}</small
>
</div>
<span class="text-2xl font-bold"
><span class="text-cfm-primary">{{
data.charts.cases.conforming
}}</span
>&nbsp;/&nbsp;{{ data.charts.cases.total }}</span
>
</div>
<Chart
type="bar"
:data="casesChartData"
:options="casesChartOptions"
class="w-[99%]"
/>
</div>
</div>
<!-- effect -->
<section>
<p class="h2 text-base">Effect</p>
<div class="flex gap-4 w-full">
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-1/2">
<p class="h2 pl-2 mb-2">Throughput Time</p>
<div v-if="data.effect.time !== null">
<p
class="pl-2 space-x-2"
v-if="data.effect.time.not_conforming === null"
>
<span
>All cases are conforming to set rules. Average throughput time
is</span
>
<span class="text-cfm-primary text-2xl font-medium">{{
data.effect.time.conforming
}}</span>
<span>days.</span>
</p>
<p
class="pl-2 space-x-2"
v-else-if="data.effect.time.conforming === null"
>
<span
>None of the cases is conforming to set rules. Average
throughput time is</span
>
<span class="text-cfm-secondary text-2xl font-medium">{{
data.effect.time.not_conforming
}}</span>
<span>days.</span>
</p>
<p class="pl-2 space-x-2 max-w-full" v-else>
<span
class="text-cfm-primary text-2xl font-medium inline-block"
>{{ data.effect.time.conforming }}</span
>
<span>vs</span>
<span
class="text-cfm-secondary text-2xl font-medium inline-block"
>{{ data.effect.time.not_conforming }}</span
>
<span>days,</span>
<span class="text-2xl font-medium inline-block">{{
data.effect.time.difference
}}</span>
<span>days of difference.</span>
</p>
</div>
</div>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-1/2">
<p class="h2 pl-2 mb-2">Activities per Case</p>
<div v-if="data.effect.tasks !== null">
<p
class="pl-2 space-x-2"
v-if="data.effect.tasks.not_conforming === null"
>
<span
>All cases are conforming to set rules. Average activities in
per cases is</span
>
<span class="text-cfm-primary text-2xl font-medium">{{
data.effect.tasks.conforming
}}</span>
.
</p>
<p
class="pl-2 space-x-2"
v-else-if="data.effect.tasks.conforming === null"
>
<span
>None of the cases is conforming to set rules. Average
activities in per cases is</span
>
<span class="text-cfm-secondary text-2xl font-medium">{{
data.effect.tasks.not_conforming
}}</span>
.
</p>
<p class="pl-2 space-x-2 max-w-full" v-else>
<span
class="text-cfm-primary text-2xl font-medium inline-block"
>{{ data.effect.tasks.conforming }}</span
>
<span>vs</span>
<span
class="text-cfm-secondary text-2xl font-medium inline-block"
>{{ data.effect.tasks.not_conforming }}</span
>
<span>activities,</span>
<span class="text-2xl font-medium inline-block">{{
data.effect.tasks.difference
}}</span>
<span>activities of difference.</span>
</p>
</div>
</div>
</div>
</section>
<!-- Loop group -->
<section>
<div v-if="data.loops === null"></div>
<div v-else>
<p class="h2 text-base">Loop List</p>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-full">
<p class="h2 pl-2 mb-2">Short Loop(s)</p>
<table class="text-sm min-w-full table-fixed">
<caption class="hidden">
Loop List
</caption>
<thead class="hidden">
<tr>
<th class="w-1/5 px-4 py-2 hidden"></th>
<th class="w-1/5 px-4 py-2 hidden"></th>
<th class="w-1/5 px-4 py-2 hidden"></th>
<th class="w-1/5 px-4 py-2 hidden"></th>
<th class="w-1/5 px-4 py-2 hidden"></th>
</tr>
</thead>
<tbody>
<tr v-for="(trace, key) in data.loops" :key="key">
<td class="p-2 pl-6 truncate max-w-0 w-1/3">
<span
class="material-symbols-outlined disc !text-sm align-middle mr-1"
>fiber_manual_record</span
>{{ trace.label }}
</td>
<td class="p-2 min-w-[96px] w-2/5">
<div
class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden"
>
<div class="h-full bg-primary" :style="trace.value"></div>
</div>
</td>
<td class="p-2 text-right truncate">{{ trace.count }}</td>
<td class="p-2 text-right">{{ trace.ratio }}%</td>
<td class="p-2 text-center">
<div
class="btn btn-sm btn-c-primary cursor-pointer"
@click="openLoopMore(trace.no)"
>
More
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- Issues group -->
<section>
<div v-if="data.issues === 'reset'">
<p class="h2 text-base">Non-conformance Issues</p>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-full">
<p class="h2 pl-2 mb-2">Issue List</p>
</div>
</div>
<div v-else>
<div>
<div
v-if="
(data.issues?.length === 0 || data.issues === null) &&
data.timeTrend.chart === null
"
></div>
<p v-else class="h2 text-base">Non-conformance Issues</p>
</div>
<div
class="flex w-full"
:class="
data.issues === null || data.issues?.length === 0 ? '' : 'gap-4'
"
>
<!-- Issues chart -->
<div
v-if="data.timeTrend.chart !== null"
class="border rounded border-neutral-300 p-2 bg-neutral-10"
:class="
data.issues === null || data.issues?.length === 0
? 'w-full'
: 'w-1/2'
"
>
<p class="h2 p-2 flex justify-between items-center">
<span
>Time Trend<span
class="material-symbols-outlined !text-sm align-middle ml-2"
v-tooltip.bottom="tooltip.timeTrend"
>info</span
></span
>
<span class="text-2xl"
><span class="text-cfm-secondary">{{
data.timeTrend.not_conforming
}}</span
>&nbsp;/&nbsp;{{ data.timeTrend.total }}</span
>
</p>
<Chart
type="line"
:data="timeChartData"
:options="timeChartOptions"
class="w-[99%]"
/>
</div>
<!-- Issues list -->
<div v-if="data.issues === null || data.issues?.length === 0"></div>
<div
v-else
class="border rounded border-neutral-300 p-2 bg-neutral-10"
:class="data.timeTrend.chart !== null ? 'w-1/2' : 'w-full'"
>
<p class="h2 pl-2 mb-2">
Issue List<span
class="material-symbols-outlined !text-sm align-middle ml-2 cursor-pointer"
v-tooltip.bottom="tooltip.issueList"
>info</span
>
</p>
<table class="text-sm min-w-full table-fixed">
<caption class="hidden">
Issues List
</caption>
<thead class="hidden">
<tr>
<th class="w-1/5 px-4 py-2 hidden"></th>
<th class="w-1/5 px-4 py-2 hidden"></th>
<th class="w-1/5 px-4 py-2 hidden"></th>
<th class="w-1/5 px-4 py-2 hidden"></th>
<th class="w-1/5 px-4 py-2 hidden"></th>
</tr>
</thead>
<tbody>
<tr v-for="(trace, key) in data.issues" :key="key">
<td class="p-2 pl-6 truncate max-w-0 w-1/3">
<span
class="material-symbols-outlined disc !text-sm align-middle mr-1"
>fiber_manual_record</span
>{{ trace.label }}
</td>
<td class="p-2 min-w-[96px] w-2/5">
<div
class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden"
>
<div
class="h-full bg-cfm-secondary"
:style="trace.value"
></div>
</div>
</td>
<td class="p-2 text-right truncate">{{ trace.count }}</td>
<td class="p-2 text-right">{{ trace.ratio }}%</td>
<td class="p-2 text-center">
<div
class="btn btn-sm btn-cfm-secondary cursor-pointer"
@click="openMore(trace.no)"
>
More
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<MoreModal
:listModal="issuesModal"
@closeModal="issuesModal = $event"
:listTraces="issueTraces"
:taskSeq="taskSeq"
:cases="cases"
:listNo="issuesNo"
:traceId="traceId"
:firstCases="firstCases"
:category="'issue'"
></MoreModal>
<MoreModal
:listModal="loopModal"
@closeModal="loopModal = $event"
:listTraces="loopTraces"
:taskSeq="loopTaskSeq"
:cases="loopCases"
:listNo="loopNo"
:traceId="looptraceId"
:firstCases="loopFirstCases"
:category="'loop'"
></MoreModal>
</section>
</template>
<script setup>
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
// imacat.yang@dsp.im (imacat), 2023/9/23
/**
* @module components/Discover/Conformance/ConformanceResults
* Conformance checking results panel displaying rule
* check outcomes in a data table with status indicators.
*/
import { ref, watch } from "vue";
import { storeToRefs } from "pinia";
import { useConformanceStore } from "@/stores/conformance";
import iconNA from "@/components/icons/IconNA.vue";
import MoreModal from "./MoreModal.vue";
import getNumberLabel from "@/module/numberLabel.js";
import {
setLineChartData,
setBarChartData,
timeRange,
yTimeRange,
getXIndex,
formatTime,
formatMaxTwo,
} from "@/module/setChartData.js";
import shortScaleNumber from "@/module/shortScaleNumber.js";
import getMoment from "moment";
import emitter from "@/utils/emitter";
const conformanceStore = useConformanceStore();
const {
conformanceTempReportData,
issueTraces,
taskSeq,
cases,
loopTraces,
loopTaskSeq,
loopCases,
} = storeToRefs(conformanceStore);
const data = ref({
total: "--",
counts: {
conforming: "--",
not_conforming: "--",
not_applicable: 0,
},
charts: {
rate: {
rate: "--",
chart: {},
},
cases: {
conforming: "--",
total: "--",
chart: {},
},
fitness: "--",
},
effect: {
time: null,
tasks: null,
},
loops: null,
issues: "reset",
timeTrend: {
not_conforming: "--",
total: "--",
chart: {},
},
});
const isCoverPlate = ref(false);
const issuesModal = ref(false);
const loopModal = ref(false);
const rateChartData = ref(null);
const rateChartOptions = ref(null);
const casesChartData = ref(null);
const casesChartOptions = ref(null);
const timeChartData = ref(null);
const timeChartOptions = ref(null);
const issuesNo = ref(null);
const traceId = ref(null);
const firstCases = ref(null);
const loopNo = ref(null);
const looptraceId = ref(null);
const loopFirstCases = ref(null);
const selectDurationTime = ref(null);
const tooltip = ref({
rate: {
value: "= Conforming / (Conforming + Not Conforming) * 100%",
class: "!max-w-[36rem] !text-[10px] !opacity-90",
},
case: {
value: "= Conforming / (Conforming + Not Conforming)",
class: "!max-w-[36rem] !text-[10px] !opacity-90",
},
timeTrend: {
value: "=Not Conforming / (total Conforming+total Not Conforming)",
class: "!max-w-[36rem] !text-[10px] !opacity-90",
},
issueList: {
value:
"Percentage of Issue Type (%) = Cases of Issue Type / Total Cases of All Issue Types.",
class: "!max-w-[36rem] !text-[10px] !opacity-90",
},
});
/**
* set progress bar width
* @param {number} value - The percentage value.
* @returns {string} The CSS width style string.
*/
const progressWidth = (value) => {
return `width:${value}%;`;
};
/**
* Number to percentage
* @param {number} val - The raw ratio value.
* @returns {string} The formatted percentage string.
*/
const getPercentLabel = (val) => {
if ((val * 100).toFixed(1) >= 100) return 100;
else return parseFloat((val * 100).toFixed(1));
};
/**
* Convert seconds to days
* @param {number} sec - The number of seconds.
* @returns {number} day
*/
const convertSecToDay = (sec) => {
return sec / 86400;
};
/**
* Open Issues Modal.
* @param {number} no - The trace number.
*/
const openMore = async (no) => {
// Use async/await to prevent errors caused by asynchronous data not being available yet
issuesNo.value = no;
await conformanceStore.getConformanceIssue(no);
if (issueTraces.value.length === 0) return;
traceId.value = issueTraces.value[0].id;
firstCases.value = await conformanceStore.getConformanceTraceDetail(
no,
issueTraces.value[0].id,
0,
);
issuesModal.value = true;
};
/**
* Open Loop Modal.
* @param {number} no - The trace number.
*/
const openLoopMore = async (no) => {
// Use async/await to prevent errors caused by asynchronous data not being available yet
loopNo.value = no;
await conformanceStore.getConformanceLoop(no);
if (loopTraces.value.length === 0) return;
looptraceId.value = loopTraces.value[0].id;
loopFirstCases.value = await conformanceStore.getConformanceLoopsTraceDetail(
no,
loopTraces.value[0].id,
0,
);
loopModal.value = true;
};
/**
* set conformance report data
* @param {object} data - The report data received from the backend.
*/
const setConformanceTempReportData = (newData) => {
const total = getNumberLabel(
Object.values(newData.counts).reduce((acc, val) => acc + val, 0),
);
const sum = newData.counts.conforming + newData.counts.not_conforming;
const rate = ((newData.counts.conforming / sum) * 100).toFixed(1);
const isNullTime = (value) =>
value === null ? null : getNumberLabel((value / 86400).toFixed(1));
const isNullCase = (value) =>
value === null ? null : getNumberLabel(value.toFixed(1));
const setLoopData = (value) =>
value.map((item) => {
return {
no: item.no,
label: item.description,
value: `width:${getPercentLabel(item.count / newData.counts.conforming)}%;`,
count: getNumberLabel(item.count),
ratio: getPercentLabel(item.count / newData.counts.conforming),
};
});
const setIssueData = (value) =>
value.map((item) => {
return {
no: item.no,
label: item.description,
value: `width:${getPercentLabel(item.count / newData.counts.not_conforming)}%;`,
count: getNumberLabel(item.count),
ratio: getPercentLabel(item.count / newData.counts.not_conforming),
};
});
const isNullLoops = (value) => (value === null ? null : setLoopData(value));
const isNullIsssue = (value) => (value === null ? null : setIssueData(value));
const result = {
total: `Total ${total}`,
counts: {
conforming: getNumberLabel(newData.counts.conforming),
not_conforming: getNumberLabel(newData.counts.not_conforming),
not_applicable: getNumberLabel(newData.counts.not_applicable),
},
charts: {
rate: {
rate: rate,
data: setLineChartData(
newData.charts.rate.data,
newData.charts.rate.x_axis.max,
newData.charts.rate.x_axis.min,
true,
),
xMax: getMoment(newData.charts.rate.x_axis.max).format("YYYY/M/D"),
xMin: getMoment(newData.charts.rate.x_axis.min).format("YYYY/M/D"),
},
cases: {
conforming: getNumberLabel(newData.counts.conforming),
total: getNumberLabel(sum),
data: {
conforming: setBarChartData(
newData.charts.cases.data
.filter((item) => item.label === "conforming")
.map((item) => item.data)[0],
),
not_conforming: setBarChartData(
newData.charts.cases.data
.filter((item) => item.label === "not-conforming")
.map((item) => item.data)[0],
),
},
xMax: getMoment(newData.charts.cases.x_axis.max).format("YYYY/M/D"),
xMin: getMoment(newData.charts.cases.x_axis.min).format("YYYY/M/D"),
},
fitness: getNumberLabel(newData.charts.fitness),
},
effect: {
time: {
conforming: isNullTime(newData.effect.time.conforming),
not_conforming: isNullTime(newData.effect.time.not_conforming),
difference: (
isNullTime(newData.effect.time.conforming) -
isNullTime(newData.effect.time.not_conforming)
).toFixed(1),
},
tasks: {
conforming: isNullCase(newData.effect.tasks.conforming),
not_conforming: isNullCase(newData.effect.tasks.not_conforming),
difference: (
isNullCase(newData.effect.tasks.conforming) -
isNullCase(newData.effect.tasks.not_conforming)
).toFixed(1),
},
},
loops: isNullLoops(newData.loops),
issues: isNullIsssue(newData.issues),
timeTrend: {
not_conforming: getNumberLabel(newData.counts.not_conforming),
total: getNumberLabel(sum),
chart: null,
xMax: null,
xMin: null,
yMax: null,
yMin: null,
},
};
if (newData.charts.time) {
result.timeTrend.chart = setLineChartData(
newData.charts.time.data,
newData.charts.time.x_axis.max,
newData.charts.time.x_axis.min,
false,
newData.charts.time.y_axis.max,
newData.charts.time.y_axis.min,
);
result.timeTrend.xMax = newData.charts.time.x_axis.max;
result.timeTrend.xMin = newData.charts.time.x_axis.min;
result.timeTrend.yMax = newData.charts.time.y_axis.max;
result.timeTrend.yMin = newData.charts.time.y_axis.min;
}
setRateChartData(result.charts.rate.data); // Build the Rate Chart.js chart
setCasesChartData(
result.charts.cases.data.conforming,
result.charts.cases.data.not_conforming,
newData.charts.cases.x_axis.max,
newData.charts.cases.x_axis.min,
); // Build the Cases Chart.js chart
if (newData.charts.time)
setTimeChartData(
result.timeTrend.chart,
result.timeTrend.xMax,
result.timeTrend.xMin,
result.timeTrend.yMax,
result.timeTrend.yMin,
); // Build the Time Chart.js chart
return result;
};
/**
* set Rate Chart Data
* @param {object} data new rate chart data
*/
const setRateChartData = (chartData) => {
rateChartData.value = {
labels: [],
datasets: [
{
label: "Rate",
data: chartData,
fill: false,
pointRadius: 0, // Hide data points
pointHoverRadius: 0, // Hide data points on hover
tension: 0.4,
borderColor: "#0099FF",
x: "x",
y: "y",
},
],
};
rateChartOptions.value = {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 0.6,
layout: {
padding: {
top: 16,
left: 8,
right: 8,
},
},
plugins: {
legend: false, // Hide legend
tooltip: {
enabled: false, // Hide tooltip
},
},
scales: {
x: {
type: "time",
ticks: {
display: false,
},
grid: {
display: false, // Hide x-axis grid lines
},
border: {
color: "#334155",
},
},
y: {
beginAtZero: true, // Scale includes 0
suggestedMin: 0,
suggestedMax: 1,
ticks: {
// Set tick intervals
includeBounds: true,
color: "#334155",
align: "inner",
callback: function (value, index, values) {
if (value === 0 || value === 1) {
return `${value * 100}%`;
}
},
},
grid: {
display: false, // Hide y-axis grid lines
},
border: {
color: "#334155",
},
},
},
};
};
/**
* set Cases Chart Data
* @param {array} conformingData new cases chart conforming data
* @param {array} notConformingData new cases chart not conforming data
* @param {number} xMax new cases chart xMax
* @param {number} xMin new cases chart xMin
*/
const setCasesChartData = (conformingData, notConformingData, xMax, xMin) => {
casesChartData.value = {
datasets: [
{
type: "bar",
label: "Conforming",
data: conformingData,
backgroundColor: "#0099FF",
},
{
type: "bar",
label: "Not Conforming",
data: notConformingData,
backgroundColor: "#FFAA44",
},
],
};
casesChartOptions.value = {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 0.8,
layout: {
padding: {
top: 16,
left: 8,
right: 8,
},
},
plugins: {
tooltips: {
mode: "index",
intersect: false,
},
legend: false, // Hide legend
},
scales: {
x: {
stacked: true,
ticks: {
display: false,
},
grid: {
display: false, // Hide x-axis grid lines
},
border: {
color: "#334155",
},
},
y: {
stacked: true,
beginAtZero: true, // Scale includes 0
ticks: {
color: "#334155",
align: "inner",
callback: function (value, index, values) {
if (index === 0 || index === values.length - 1) {
return shortScaleNumber(value);
}
},
},
grid: {
// display: false, // Hide y-axis grid lines
color: function (context) {
return context.tick.value === 0 ? "#334155" : null;
},
drawTicks: false,
},
border: {
color: "#334155",
},
},
},
};
};
/**
* set Time Trend chart data
* @param {array} chartData Time Trend chart conforming data
* @param {number} xMax Time Trend xMax
* @param {number} xMin Time Trend xMin
* @param {number} yMax Time Trend yMax
* @param {number} yMin Time Trend yMin
*/
const setTimeChartData = (chartData, xMax, xMin, yMax, yMin) => {
const max = yMax * 1.1;
const xVal = timeRange(xMin, xMax, 100);
const yVal = yTimeRange(chartData, 100, yMin, yMax);
xVal.map((x, index) => ({ x, y: yVal[index] }));
let formattedXVal = xVal.map((value) => formatTime(value));
formattedXVal = formatMaxTwo(formattedXVal);
const selectTimeMinIndex = getXIndex(xVal, selectDurationTime.value.min);
const selectTimeMaxIndex = getXIndex(xVal, selectDurationTime.value.max);
const start = selectTimeMinIndex;
const end = selectTimeMaxIndex;
const inside = (ctx, value) =>
ctx.p0DataIndex >= start && ctx.p1DataIndex <= end ? value : undefined;
const outside = (ctx, value) =>
ctx.p0DataIndex < start || ctx.p1DataIndex > end ? value : undefined;
timeChartData.value = {
labels: formattedXVal,
datasets: [
{
label: "Conforming",
data: yVal,
fill: true,
showLine: false,
tension: 0.4,
backgroundColor: "rgba(0,153,255)",
pointRadius: 0,
pointHitRadius: 0,
spanGaps: true,
segment: {
backgroundColor: (ctx) =>
inside(ctx, "rgb(0,153,255)") || outside(ctx, "rgb(255,170,68)"),
},
x: "x",
y: "y",
},
],
};
timeChartOptions.value = {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: {
top: 16,
left: 8,
right: 8,
},
},
plugins: {
legend: false, // Hide legend
tooltip: false,
},
scales: {
x: {
ticks: {
maxRotation: 0, // Do not rotate labels (0~50)
color: "#334155",
display: true,
},
grid: {
display: false, // Hide x-axis grid lines
},
title: {
display: true,
text: "Time",
color: "rgba(100,116,139)",
},
},
y: {
beginAtZero: true, // Scale includes 0
max: max,
ticks: {
// Set tick intervals
display: false, // Hide values, only show grid lines
stepSize: max / 4,
},
grid: {
color: "rgba(100,116,139)",
drawTicks: false, // Hide extra space on the left
},
border: {
display: false, // Hide the extra border line on the left
},
title: {
display: true,
text: "Occurrences",
color: "rgba(100,116,139)",
},
},
},
};
};
// watch
watch(conformanceTempReportData, (newValue) => {
if (newValue?.rule && newValue.rule.min !== null) {
selectDurationTime.value = {
min: newValue.rule.min,
max: newValue.rule.max,
};
}
data.value = setConformanceTempReportData(newValue);
});
// created - emitter listeners
emitter.on("coverPlate", (boolean) => {
isCoverPlate.value = boolean;
});
// Get selectTimeRange for use by Time Trend
emitter.on(
"timeRangeMaxMin",
(newData) => (selectDurationTime.value = newData),
);
</script>
<style scoped>
:deep(.disc) {
font-variation-settings:
"FILL" 1,
"wght" 100,
"GRAD" 0,
"opsz" 20;
}
</style>