ui/src/components/view/stats/ResourceStatsLineChart.vue (235 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<Line
:chart-options="preparedOptions"
:chart-data="preparedData"
:width="chartWidth"
:height="chartHeight"
/>
</template>
<script>
import { Line } from 'vue-chartjs'
import { Chart as ChartJS, Title, Tooltip, Legend, LineElement, CategoryScale, TimeScale, LinearScale, PointElement, Filler } from 'chart.js'
ChartJS.register(Title, Tooltip, Legend, LineElement, CategoryScale, TimeScale, LinearScale, PointElement, Filler)
export default {
name: 'ResourceStatsLineChart',
components: { Line },
props: {
chartData: {
type: Object,
required: true
},
chartLabels: {
type: Array,
required: true
},
yAxisMeasurementUnit: {
type: String,
required: true
},
yAxisInitialMax: {
type: Number,
default: 1
},
yAxisIncrementValue: {
type: Number,
default: 1
},
chartWidth: {
type: Number,
default: 1024
},
chartHeight: {
type: Number,
default: 250
}
},
computed: {
preparedData () {
if (this.chartData) {
return this.prepareData(this.chartData)
}
return {}
},
preparedOptions () {
if (this.chartData) {
return this.getChartOptions(this.calculateMaxYAxisAndStepSize(this.chartData, this.yAxisInitialMax, this.yAxisIncrementValue), this.yAxisMeasurementUnit)
}
return {}
}
},
methods: {
/**
* Converts a value (Byte-based) from an unit to other one. For example: from Byte to KiB; from GiB to MiB; etc.
* To use it consider the following sequence: Byte -> KiB -> MiB -> GiB ...
* So, from Byte to MiB there are 2 steps, while from MiB to Byte there are -2 steps.
* @param value the value to be converted.
* @param step the number of steps between Byte-based units of measure.
* @returns the converted value.
*/
convertByteBasedUnitOfMeasure (value, step) {
if (value === 0) {
return 0.00
}
if (step === 0) {
return value
}
if (step > 0) {
return parseFloat(value / (Math.pow(1024, step))).toFixed(2)
}
return parseFloat(value * (Math.pow(1024, Math.abs(step)))).toFixed(2)
},
calculateMaxYAxisAndStepSize (chartLines, initialMaxYAxis, incrementValue) {
const numberOfLabelsOnYaxis = 4
var highestValue = 0
var maxYAxis = initialMaxYAxis
for (const line of chartLines) {
for (const d of line.data) {
const currentValue = parseFloat(d.stat)
if (currentValue > highestValue) {
highestValue = currentValue
while (highestValue > maxYAxis) {
maxYAxis += incrementValue
if (maxYAxis % incrementValue !== 0) {
maxYAxis = Math.round(maxYAxis / incrementValue) * incrementValue
}
}
}
}
}
return { maxYAxes: maxYAxis, stepSize: maxYAxis / numberOfLabelsOnYaxis }
},
/**
* Returns the chart options.
* @param yAxesStepSize the step size for the Y axes.
* @param yAxesUnitOfMeasurement the unit of measurement label used on the Y axes.
* @returns the chart options.
*/
getChartOptions (yAxesOptions, yAxesUnitOfMeasurement) {
const dateTimes = this.convertStringArrayToDateArray(JSON.parse(JSON.stringify(this.chartLabels)))
const averageDifference = this.averageDifferenceBetweenTimes(dateTimes)
const xAxisStepSize = this.calculateStepSize(this.chartLabels.length, averageDifference)
const startDate = new Date(dateTimes[0])
const endDate = new Date(dateTimes[dateTimes.length - 1])
const differentDay = startDate.getDate() !== endDate.getDate()
const differentYear = startDate.getFullYear() !== endDate.getFullYear()
var displayFormat = 'HH:mm'
if (xAxisStepSize < 5 * 60) {
displayFormat += ':ss'
}
if (differentDay) {
displayFormat = 'MMM-DD ' + displayFormat
}
if (xAxisStepSize >= 24 * 60 * 60) {
displayFormat = 'MMM-DD'
}
if (differentYear) {
displayFormat = 'YYYY-' + displayFormat
}
var chartOptions = {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxis: {
min: 0,
max: yAxesOptions.maxYAxes,
reverse: false,
ticks: {
stepSize: yAxesOptions.stepSize,
callback: function (label) {
return label + yAxesUnitOfMeasurement
}
}
},
xAxis: {
type: 'time',
autoSkip: false,
time: {
parser: 'YYYY-MM-DD HH:mm:ss',
unit: 'second',
displayFormats: {
second: displayFormat
}
}
}
}
}
chartOptions.scales.xAxis.time.stepSize = xAxisStepSize
return chartOptions
},
convertStringArrayToDateArray (stringArray) {
const dateArray = []
for (const element of stringArray) {
dateArray.push(new Date(element.replace(' ', 'T')))
}
return dateArray
},
averageDifferenceBetweenTimes (timeList) {
const oneSecond = 1000 // 1 second represented as milliseconds
const differences = []
var previous = timeList.splice(0, 1)[0]
for (const time of timeList) {
differences.push((time - previous) / oneSecond) // push the difference in seconds
previous = time
}
if (differences.length === 0) {
return 1
}
const averageDifference = Math.trunc(differences.reduce((a, b) => a + b, 0) / differences.length)
return averageDifference
},
calculateStepSize (numberOfDataPoints, differenceBetweenTimes) {
const idealNumberOfLabels = 8
const result = numberOfDataPoints / idealNumberOfLabels
if (result > 1) {
return result * differenceBetweenTimes
}
return differenceBetweenTimes
},
prepareData (chartData) {
const datasetList = []
for (const element of chartData) {
datasetList.push(
{
backgroundColor: element.backgroundColor,
borderColor: element.borderColor,
borderWidth: 3,
label: element.label,
data: element.data.map(d => d.stat),
hidden: this.hideLine(element.data.map(d => d.stat)),
pointRadius: element.pointRadius,
fill: 'origin'
}
)
}
return {
labels: this.chartLabels,
datasets: datasetList
}
},
hideLine (data) {
for (const d of data) {
if (d < 0) {
return true
}
}
return false
}
}
}
</script>