Files
lucia-frontend/src/views/Discover/Performance/FreqChart.vue
2026-03-08 12:11:57 +08:00

299 lines
7.7 KiB
Vue

<template>
<Chart
type="line"
:data="primeVueSetDataState"
:options="primeVueSetOptionsState"
class="h-96"
/>
</template>
<script setup>
// The Lucia project.
// Copyright 2024-2026 DSP, inc. All rights reserved.
// Authors:
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
// imacat.yang@dsp.im (imacat), 2023/9/23
/**
* @module views/Discover/Performance/FreqChart
* Frequency chart component displaying activity
* occurrence data with Chart.js bar charts.
*/
import { ref, onMounted } from "vue";
import {
setTimeStringFormatBaseOnTimeDifference,
mapTimestampToAxisTicksByFormat,
} from "@/module/timeLabel.js";
const knownScaleLineChartOptions = {
x: {
type: "time",
title: {
display: true,
color: "#334155",
font: {
size: 12,
lineHeight: 2,
},
},
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
},
},
ticks: {
display: true,
maxRotation: 0, // Do not rotate labels (range: 0~50)
color: "#64748b",
source: "labels", // Dynamically display label count proportionally
},
border: {
color: "#64748b",
},
grid: {
tickLength: 0, // Prevent grid lines from extending beyond the axis
},
},
y: {
beginAtZero: true, // Scale includes 0
title: {
display: true,
color: "#334155",
font: {
size: 12,
lineHeight: 2,
},
},
ticks: {
color: "#64748b",
padding: 8,
},
grid: {
color: "#64748b",
},
border: {
display: false, // Hide the extra border line on the left side
},
},
};
const props = defineProps({
chartData: {
type: Object,
},
content: {
type: Object,
},
yUnit: {
type: String,
},
pageName: {
type: String,
},
});
const primeVueSetDataState = ref(null);
const primeVueSetOptionsState = ref(null);
const colorPrimary = ref("#0099FF");
const colorSecondary = ref("#FFAA44");
/**
* Compare page and Performance have this same function.
* @param whichScaleObj PrimeVue scale option object to reference to
* @param customizeOptions
* @param customizeOptions.content
* @param customizeOptions.ticksOfXAxis
*/
const getCustomizedScaleOption = (
whichScaleObj,
{ customizeOptions: { content, ticksOfXAxis } },
) => {
let resultScaleObj;
resultScaleObj = customizeScaleChartOptionTitleByContent(
whichScaleObj,
content,
);
resultScaleObj = customizeScaleChartOptionTicks(resultScaleObj, ticksOfXAxis);
return resultScaleObj;
};
/**
* Compare page and Performance have this same function.
* @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.
*/
const customizeScaleChartOptionTicks = (scaleObjectToAlter, ticksOfXAxis) => {
return {
...scaleObjectToAlter,
x: {
...scaleObjectToAlter.x,
ticks: {
...scaleObjectToAlter.x.ticks,
callback: function (value, index) {
// Customize x-axis time ticks based on different intervals
return ticksOfXAxis[index];
},
},
},
};
};
/** Compare page and Performance have this same function.
* Customizes a base tooltip object using the content data.
* The object order is known to be: x, then title, then 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
*/
const 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,
},
},
};
};
/**
* Builds the PrimeVue line chart data and options configuration.
* @param {object} chartData - The chart data from the API.
* @param {object} content - The axis label content.
* @param {string} pageName - 'Compare' or page identifier.
*/
const getLineChartPrimeVueSetting = (chartData, content, pageName) => {
let datasetsArr;
let datasets;
let datasetsPrimary; // For Compare page case
let datasetsSecondary; // For Compare page case
const minX = chartData?.x_axis?.min;
const maxX = chartData?.x_axis?.max;
let xData;
let primeVueSetData = {};
let primeVueSetOption = {};
// Consider the dimension of chartData.data
// When handling the Compare page case
if (pageName === "Compare") {
datasetsPrimary = chartData.data[0].data;
datasetsSecondary = chartData.data[1].data;
datasetsArr = [
{
label: chartData.data[0].label,
data: datasetsPrimary,
fill: false,
tension: 0, // Bezier curve tension
borderColor: colorPrimary,
pointBackgroundColor: colorPrimary,
},
{
label: chartData.data[1].label,
data: datasetsSecondary,
fill: false,
tension: 0, // Bezier curve tension
borderColor: colorSecondary,
pointBackgroundColor: colorSecondary,
},
];
xData = chartData.data[0].data.map((item) => new Date(item.x).getTime());
} else {
datasets = chartData.data;
datasetsArr = [
{
label: content.title,
data: datasets,
fill: false,
tension: 0, // Bezier curve tension
borderColor: "#0099FF",
},
];
xData = chartData.data.map((item) => new Date(item.x).getTime());
}
// Customize X axis ticks due to different differences between min and max of data group
// Compare page and Performance page share the same logic
const formatToSet = setTimeStringFormatBaseOnTimeDifference(minX, maxX);
const ticksOfXAxis = mapTimestampToAxisTicksByFormat(xData, formatToSet);
const customizedScaleOption = getCustomizedScaleOption(
knownScaleLineChartOptions,
{
customizeOptions: {
content,
ticksOfXAxis,
},
},
);
primeVueSetData = {
labels: xData,
datasets: datasetsArr,
};
primeVueSetOption = {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: {
top: 16,
left: 8,
right: 8,
},
},
plugins: {
legend: false, // Legend
tooltip: {
displayColors: true,
titleFont: { weight: "normal" },
callbacks: {
label: function (tooltipItem) {
// Get the data
const label = tooltipItem.dataset.label || "";
// Build the tooltip label with dataset color indicator
return `${label}: ${tooltipItem.parsed.y}`; // Use Unicode block to represent color
},
},
},
title: {
display: false,
},
},
scales: customizedScaleOption,
};
primeVueSetOption.scales.y.ticks.precision = 0; // Show 0 decimal places on y-axis
primeVueSetOption.scales.y.ticks.callback = function (value, index, ticks) {
return value; // Y-axis ticks here have no time unit suffix
};
primeVueSetDataState.value = primeVueSetData;
primeVueSetOptionsState.value = primeVueSetOption;
};
onMounted(() => {
getLineChartPrimeVueSetting(props.chartData, props.content, props.pageName);
});
</script>