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>