web/satellite: migrate VChart component to use SFC composition API

Upgraded chart.js dependency to V4.
Removed vue-chart and types/chart.js dependencies.
Refactored VChart component to rely only on chart.js dep without any additional wrappers.
Refactored VChart component to use SFC composition API.

Change-Id: Ic5e0131bff413f3205d4449db930644d113fe36d
This commit is contained in:
Vitalii 2023-03-14 16:36:19 +02:00 committed by Storj Robot
parent 6c49cc883e
commit 37d7c0efbf
7 changed files with 238 additions and 469 deletions

View File

@ -12,7 +12,7 @@
"@hcaptcha/vue-hcaptcha": "0.3.2",
"aws-sdk": "2.1128.0",
"bip39": "3.0.4",
"chart.js": "2.9.4",
"chart.js": "4.2.1",
"core-js": "3.22.4",
"graphql": "15.3.0",
"load-script": "1.0.0",
@ -24,7 +24,6 @@
"stripe": "8.215.0",
"util": "0.12.4",
"vue": "2.7.10",
"vue-chartjs": "3.5.1",
"vue-class-component": "7.2.6",
"vue-clipboard2": "0.3.3",
"vue-fragment": "1.6.0",
@ -35,7 +34,6 @@
"vuex": "3.6.2"
},
"devDependencies": {
"@types/chart.js": "2.9.36",
"@types/filesystem": "0.0.32",
"@types/jest": "27.5.0",
"@types/node": "16.18.14",
@ -2705,6 +2703,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
},
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@ -2972,14 +2975,6 @@
"@types/node": "*"
}
},
"node_modules/@types/chart.js": {
"version": "2.9.36",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.36.tgz",
"integrity": "sha512-lkG9C6O5TqKyW3JDWMfCedVUATH4AeQCo8Q/vflT/0VDj6KMcba+jr1wLe+IXQ+F9mVWn80DVsDnt6cPuGutIg==",
"dependencies": {
"moment": "^2.10.2"
}
},
"node_modules/@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
@ -5605,29 +5600,14 @@
}
},
"node_modules/chart.js": {
"version": "2.9.4",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz",
"integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz",
"integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==",
"dependencies": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
"@kurkle/color": "^0.3.0"
},
"node_modules/chartjs-color": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
"integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
"dependencies": {
"chartjs-color-string": "^0.6.0",
"color-convert": "^1.9.3"
}
},
"node_modules/chartjs-color-string": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
"integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
"dependencies": {
"color-name": "^1.0.0"
"engines": {
"pnpm": "^7.0.0"
}
},
"node_modules/chokidar": {
@ -5945,6 +5925,7 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@ -5952,7 +5933,8 @@
"node_modules/color-convert/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/color-name": {
"version": "1.1.4",
@ -13417,14 +13399,6 @@
"integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==",
"dev": true
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": {
"node": "*"
}
},
"node_modules/mrmime": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
@ -17784,21 +17758,6 @@
"csstype": "^3.1.0"
}
},
"node_modules/vue-chartjs": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-3.5.1.tgz",
"integrity": "sha512-foocQbJ7FtveICxb4EV5QuVpo6d8CmZFmAopBppDIGKY+esJV8IJgwmEW0RexQhxqXaL/E1xNURsgFFYyKzS/g==",
"dependencies": {
"@types/chart.js": "^2.7.55"
},
"engines": {
"node": ">=6.9.0",
"npm": ">= 3.0.0"
},
"peerDependencies": {
"chart.js": ">= 2.5"
}
},
"node_modules/vue-class-component": {
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz",
@ -21016,6 +20975,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
},
"@leichtgewicht/ip-codec": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@ -21243,14 +21207,6 @@
"@types/node": "*"
}
},
"@types/chart.js": {
"version": "2.9.36",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.36.tgz",
"integrity": "sha512-lkG9C6O5TqKyW3JDWMfCedVUATH4AeQCo8Q/vflT/0VDj6KMcba+jr1wLe+IXQ+F9mVWn80DVsDnt6cPuGutIg==",
"requires": {
"moment": "^2.10.2"
}
},
"@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
@ -23258,29 +23214,11 @@
"dev": true
},
"chart.js": {
"version": "2.9.4",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz",
"integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz",
"integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==",
"requires": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
},
"chartjs-color": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
"integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
"requires": {
"chartjs-color-string": "^0.6.0",
"color-convert": "^1.9.3"
}
},
"chartjs-color-string": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
"integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
"requires": {
"color-name": "^1.0.0"
"@kurkle/color": "^0.3.0"
}
},
"chokidar": {
@ -23522,6 +23460,7 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
},
@ -23529,7 +23468,8 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
}
}
},
@ -29128,11 +29068,6 @@
"integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==",
"dev": true
},
"moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
},
"mrmime": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
@ -32329,14 +32264,6 @@
"csstype": "^3.1.0"
}
},
"vue-chartjs": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-3.5.1.tgz",
"integrity": "sha512-foocQbJ7FtveICxb4EV5QuVpo6d8CmZFmAopBppDIGKY+esJV8IJgwmEW0RexQhxqXaL/E1xNURsgFFYyKzS/g==",
"requires": {
"@types/chart.js": "^2.7.55"
}
},
"vue-class-component": {
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz",

View File

@ -17,7 +17,7 @@
"@hcaptcha/vue-hcaptcha": "0.3.2",
"aws-sdk": "2.1128.0",
"bip39": "3.0.4",
"chart.js": "2.9.4",
"chart.js": "4.2.1",
"core-js": "3.22.4",
"graphql": "15.3.0",
"load-script": "1.0.0",
@ -29,7 +29,6 @@
"stripe": "8.215.0",
"util": "0.12.4",
"vue": "2.7.10",
"vue-chartjs": "3.5.1",
"vue-class-component": "7.2.6",
"vue-clipboard2": "0.3.3",
"vue-fragment": "1.6.0",
@ -40,7 +39,6 @@
"vuex": "3.6.2"
},
"devDependencies": {
"@types/chart.js": "2.9.36",
"@types/filesystem": "0.0.32",
"@types/jest": "27.5.0",
"@types/node": "16.18.14",

View File

@ -222,7 +222,7 @@ onMounted(() => {
@media screen and (max-width: 500px) {
display: flex;
flex-direction: column;
align-items: start;
align-items: flex-start;
justify-content: center;
gap: 10px;
}
@ -243,12 +243,12 @@ onMounted(() => {
&__actions {
display: flex;
align-items: center;
justify-content: end;
justify-content: flex-end;
gap: 5px;
@media screen and (max-width: 500px) {
width: 100%;
justify-content: start;
justify-content: flex-start;
}
}
}

View File

@ -1,192 +1,174 @@
// Copyright (C) 2021 Storj Labs, Inc.
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<script lang="ts">
import { Line } from 'vue-chartjs';
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
<template>
<canvas :id="chartId" :width="width" :height="height" />
</template>
import { ChartData, RenderChart } from '@/types/chart';
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import {
CategoryScale,
Chart as ChartJS,
LinearScale,
Tooltip as VTooltip,
LineController,
LineElement,
Filler,
PointElement,
TooltipModel,
ChartType,
ChartOptions,
ChartData,
Plugin,
} from 'chart.js';
ChartJS.register(LineElement, PointElement, VTooltip, Filler, LineController, CategoryScale, LinearScale);
const props = defineProps<{
chartId: string,
chartData: ChartData,
tooltipConstructor: (tooltipModel: TooltipModel<ChartType>) => void,
width: number,
height: number,
}>();
const chart = ref<ChartJS>();
/**
* Used to filter days displayed on x-axis.
* Returns a plugin which draws a dashed line under active datapoint.
*/
class DayShowingConditions {
public constructor(
public day: string,
public daysArray: string[],
) {}
public countMiddleDateValue(): number {
return this.daysArray.length / 2;
}
public isDayFirstOrLast(): boolean {
return this.day === this.daysArray[0] || this.day === this.daysArray[this.daysArray.length - 1];
}
public isDayAfterEighthDayOfTheMonth(): boolean {
return this.daysArray.length > 8 && this.daysArray.length <= 31;
}
}
// @vue/component
@Component({
extends: Line,
})
export default class VChart extends Vue {
@Prop({ default: () => () => { console.error('Tooltip constructor is undefined'); } })
private tooltipConstructor: (tooltipModel) => void;
@Prop({ default: {} })
private readonly chartData: ChartData;
/**
* Mounted hook after initial render.
* Adds chart plugin to draw dashed line under data point.
* Renders chart.
*/
public mounted(): void {
(this as unknown as RenderChart).addPlugin({
afterDatasetsDraw: (chart): void => {
if (chart.tooltip._active && chart.tooltip._active.length) {
const activePoint = chart.tooltip._active[0];
const afterDatasetsDrawPlugin = computed((): Plugin => {
return {
id: 'afterDatasetsDraw',
afterDatasetsDraw: (chart) => {
if (chart.tooltip) {
const activePoint = chart.tooltip.getActiveElements();
if (activePoint[0]) {
const ctx = chart.ctx;
const y_axis = chart.scales['y-axis-0'];
const tooltipPosition = activePoint.tooltipPosition();
const yAxis = chart.scales['y'];
const tooltipPosition = activePoint[0].element.tooltipPosition(true);
ctx.save();
ctx.beginPath();
ctx.setLineDash([8, 5]);
ctx.moveTo(tooltipPosition.x, tooltipPosition.y + 12);
ctx.lineTo(tooltipPosition.x, y_axis.bottom);
ctx.lineTo(tooltipPosition.x, yAxis.bottom);
ctx.lineWidth = 1;
ctx.strokeStyle = '#C8D3DE';
ctx.stroke();
ctx.restore();
}
}
},
});
(this as unknown as RenderChart).renderChart(this.chartData, this.chartOptions);
}
};
});
@Watch('chartData')
private onDataChange(_news: Record<string, unknown>, _old: Record<string, unknown>) {
/**
* renderChart method is inherited from BaseChart which is extended by VChart.Line
*/
(this as unknown as RenderChart).renderChart(this.chartData, this.chartOptions);
}
/**
/**
* Returns chart options.
*/
public get chartOptions(): Record<string, unknown> {
const filterCallback = this.filterDaysDisplayed;
const chartOptions = computed((): ChartOptions => {
return {
responsive: false,
maintainAspectRatios: false,
maintainAspectRatio: false,
animation: false,
hover: {
animationDuration: 0,
},
responsiveAnimationDuration: 0,
legend: {
display: false,
},
clip: false,
layout: {
padding: {
top: 40,
top: 20,
left: 10,
right: 10,
},
},
elements: {
point: {
radius: this.chartData.labels.length === 1 ? 10 : 0,
hoverRadius: 10,
hitRadius: 8,
},
line: {
tension: 0,
},
},
scales: {
yAxes: [{
y: {
type: 'linear',
display: true,
ticks: {
callback: function(value, _, ticks) {
const numDigits = ticks[ticks.length - 2].toString().length;
const power = Math.floor((numDigits - 1) / 3);
const result = value / Math.pow(1000, power);
return result;
border: {
display: false,
},
grid: {
display: false,
},
suggestedMin: 0,
suggestedMax: 150,
maxTicksLimit: 7,
ticks: {
font: {
family: 'sans-serif',
},
gridLines: {
maxTicksLimit: 5,
callback: function(value, _, ticks) {
const numDigits = ticks[ticks.length - 2].value.toString().length;
const power = Math.floor((numDigits - 1) / 3);
return (value as number) / Math.pow(1000, power);
},
},
},
x: {
type: 'category',
display: true,
border: {
display: false,
},
grid: {
display: false,
},
}],
xAxes: [{
display: true,
ticks: {
fontFamily: 'font_regular',
autoSkip: false,
font: {
family: 'sans-serif',
},
autoSkip: true,
maxRotation: 0,
minRotation: 0,
callback: filterCallback,
},
gridLines: {
},
},
plugins: {
legend: {
display: false,
},
}],
},
tooltips: {
tooltip: {
enabled: false,
axis: 'x',
custom: (tooltipModel) => {
this.tooltipConstructor(tooltipModel);
external: (context) => {
props.tooltipConstructor(context.tooltip);
},
},
},
};
}
});
/**
* Used as callback to filter days displayed on chart.
*/
private filterDaysDisplayed(day: string, _dayIndex: string, labelArray: string[]): string | undefined {
const eighthDayOfTheMonth = 8;
const isBeforeEighthDayOfTheMonth = labelArray.length <= eighthDayOfTheMonth;
const dayShowingConditions = new DayShowingConditions(day, labelArray);
onMounted(() => {
chart.value = new ChartJS(
document.getElementById(props.chartId) as HTMLCanvasElement,
{
type: 'line',
data: props.chartData,
options: chartOptions.value,
plugins: [afterDatasetsDrawPlugin.value],
},
);
});
if (isBeforeEighthDayOfTheMonth || this.areDaysShownOnEvenDaysAmount(dayShowingConditions)
|| this.areDaysShownOnNotEvenDaysAmount(dayShowingConditions)) {
return day;
}
}
onUnmounted(() => {
chart.value?.destroy();
});
/**
* Indicates if days are shown on even days amount.
*/
private areDaysShownOnEvenDaysAmount(dayShowingConditions: DayShowingConditions): boolean {
const isDaysAmountEven = dayShowingConditions.daysArray.length % 2 === 0;
const isDateValueInMiddleInEvenAmount = dayShowingConditions.day ===
dayShowingConditions.daysArray[dayShowingConditions.countMiddleDateValue() - 1];
return dayShowingConditions.isDayFirstOrLast() || (isDaysAmountEven
&& dayShowingConditions.isDayAfterEighthDayOfTheMonth() && isDateValueInMiddleInEvenAmount);
}
/**
* Indicates if days are shown on not even days amount.
*/
private areDaysShownOnNotEvenDaysAmount(dayShowingConditions: DayShowingConditions): boolean {
const isDaysAmountNotEven = dayShowingConditions.daysArray.length % 2 !== 0;
const isDateValueInMiddleInNotEvenAmount = dayShowingConditions.day
=== dayShowingConditions.daysArray[Math.floor(dayShowingConditions.countMiddleDateValue())];
return dayShowingConditions.isDayFirstOrLast() || (isDaysAmountNotEven
&& dayShowingConditions.isDayAfterEighthDayOfTheMonth() && isDateValueInMiddleInNotEvenAmount);
}
}
watch(() => props.chartData, () => {
chart.value?.destroy();
chart.value = new ChartJS(
document.getElementById(props.chartId) as HTMLCanvasElement,
{
type: 'line',
data: props.chartData,
options: chartOptions.value,
plugins: [afterDatasetsDrawPlugin.value],
},
);
});
</script>

View File

@ -3,8 +3,8 @@
<template>
<VChart
id="bandwidth-chart"
:key="chartKey"
chart-id="bandwidth-chart"
:chart-data="chartData"
:width="width"
:height="height"
@ -14,8 +14,9 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { ChartType, TooltipModel, ChartData } from 'chart.js';
import { ChartData, Tooltip, TooltipParams, TooltipModel, ChartTooltipData } from '@/types/chart';
import { Tooltip, TooltipParams, ChartTooltipData } from '@/types/chart';
import { DataStamp } from '@/types/projects';
import { ChartUtils } from '@/utils/chart';
@ -47,23 +48,40 @@ const chartData = computed((): ChartData => {
const secondaryData: number[] = props.allocatedData.map(el => el.value);
const xAxisDateLabels: string[] = ChartUtils.daysDisplayedOnChart(props.since, props.before);
return new ChartData(
xAxisDateLabels,
'rgba(226, 220, 255, .3)',
'#c5baff',
'#c5baff',
mainData,
'rgba(226, 220, 255, .7)',
'#a18eff',
'#a18eff',
secondaryData,
);
return {
labels: xAxisDateLabels,
datasets: [{
data: mainData,
fill: true,
backgroundColor: 'rgba(226, 220, 255, .3)',
borderColor: '#c5baff',
pointHoverBackgroundColor: '#FFFFFF',
pointBorderColor: '#c5baff',
pointHoverBorderWidth: 4,
radius: xAxisDateLabels.length === 1 ? 10 : 0,
hoverRadius: 10,
hitRadius: 8,
order: 0,
}, {
data: secondaryData,
fill: true,
backgroundColor: 'rgba(226, 220, 255, .7)',
borderColor: '#a18eff',
pointHoverBackgroundColor: '#FFFFFF',
pointBorderColor: '#a18eff',
pointHoverBorderWidth: 4,
radius: xAxisDateLabels.length === 1 ? 10 : 0,
hoverRadius: 10,
hitRadius: 8,
order: 0,
}],
};
});
/**
* Used as constructor of custom tooltip.
*/
function tooltip(tooltipModel: TooltipModel): void {
function tooltip(tooltipModel: TooltipModel<ChartType>): void {
if (!tooltipModel.dataPoints) {
const settledTooltip = Tooltip.createTooltip('settled-bandwidth-tooltip');
const allocatedTooltip = Tooltip.createTooltip('allocated-bandwidth-tooltip');
@ -90,12 +108,12 @@ function tooltip(tooltipModel: TooltipModel): void {
/**
* Returns allocated bandwidth tooltip's html mark up.
*/
function allocatedTooltipMarkUp(tooltipModel: TooltipModel): string {
function allocatedTooltipMarkUp(tooltipModel: TooltipModel<ChartType>): string {
if (!tooltipModel.dataPoints) {
return '';
}
const dataIndex = tooltipModel.dataPoints[0].index;
const dataIndex = tooltipModel.dataPoints[0].dataIndex;
const dataPoint = new ChartTooltipData(props.allocatedData[dataIndex]);
return `<div class='allocated-tooltip'>
@ -108,12 +126,12 @@ function allocatedTooltipMarkUp(tooltipModel: TooltipModel): string {
/**
* Returns settled bandwidth tooltip's html mark up.
*/
function settledTooltipMarkUp(tooltipModel: TooltipModel): string {
function settledTooltipMarkUp(tooltipModel: TooltipModel<ChartType>): string {
if (!tooltipModel.dataPoints) {
return '';
}
const dataIndex = tooltipModel.dataPoints[0].index;
const dataIndex = tooltipModel.dataPoints[0].dataIndex;
const dataPoint = new ChartTooltipData(props.settledData[dataIndex]);
return `<div class='settled-tooltip'>

View File

@ -3,8 +3,8 @@
<template>
<VChart
id="storage-chart"
:key="chartKey"
chart-id="storage-chart"
:chart-data="chartData"
:width="width"
:height="height"
@ -14,8 +14,9 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { ChartType, TooltipModel, ChartData } from 'chart.js';
import { ChartData, Tooltip, TooltipParams, TooltipModel, ChartTooltipData } from '@/types/chart';
import { Tooltip, TooltipParams, ChartTooltipData } from '@/types/chart';
import { DataStamp } from '@/types/projects';
import { ChartUtils } from '@/utils/chart';
@ -44,19 +45,27 @@ const chartData = computed((): ChartData => {
const data: number[] = props.data.map(el => el.value);
const xAxisDateLabels: string[] = ChartUtils.daysDisplayedOnChart(props.since, props.before);
return new ChartData(
xAxisDateLabels,
'#E6EDF7',
'#D7E8FF',
'#003DC1',
return {
labels: xAxisDateLabels,
datasets: [{
data,
);
fill: true,
backgroundColor: '#E6EDF7',
borderColor: '#D7E8FF',
pointHoverBackgroundColor: '#FFFFFF',
pointBorderColor: '#003DC1',
pointHoverBorderWidth: 4,
radius: xAxisDateLabels.length === 1 ? 10 : 0,
hoverRadius: 10,
hitRadius: 8,
}],
};
});
/**
* Used as constructor of custom tooltip.
*/
function tooltip(tooltipModel: TooltipModel): void {
function tooltip(tooltipModel: TooltipModel<ChartType>): void {
const tooltipParams = new TooltipParams(tooltipModel, 'storage-chart', 'storage-tooltip',
tooltipMarkUp(tooltipModel), 76, 81);
@ -66,12 +75,12 @@ function tooltip(tooltipModel: TooltipModel): void {
/**
* Returns tooltip's html mark up.
*/
function tooltipMarkUp(tooltipModel: TooltipModel): string {
function tooltipMarkUp(tooltipModel: TooltipModel<ChartType>): string {
if (!tooltipModel.dataPoints) {
return '';
}
const dataIndex = tooltipModel.dataPoints[0].index;
const dataIndex = tooltipModel.dataPoints[0].dataIndex;
const dataPoint = new ChartTooltipData(props.data[dataIndex]);
return `<div class='tooltip'>

View File

@ -1,57 +1,17 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
import { TooltipModel, ChartType } from 'chart.js';
import { DataStamp } from '@/types/projects';
import { Size } from '@/utils/bytesSize';
/**
* ChartData class holds info for ChartData entity.
*/
export class ChartData {
public labels: string[];
public datasets: DataSets[] = [];
public constructor(
labels: string[],
backgroundColor: string,
borderColor: string,
pointBorderColor: string,
data: number[],
secondaryBackgroundColor?: string,
secondaryBorderColor?: string,
secondaryPointBorderColor?: string,
secondaryData?: number[],
) {
this.labels = labels;
this.datasets[0] = new DataSets(backgroundColor, borderColor, pointBorderColor, data);
if (secondaryData && secondaryBackgroundColor && secondaryBorderColor && secondaryPointBorderColor) {
this.datasets[1] = new DataSets(secondaryBackgroundColor, secondaryBorderColor, secondaryPointBorderColor, secondaryData);
}
}
}
/**
* DataSets class holds info for chart's DataSets entity.
*/
class DataSets {
public constructor(
public backgroundColor: string,
public borderColor: string,
public pointBorderColor: string,
public data: number[],
public borderWidth: number = 4,
public pointHoverBackgroundColor: string = 'white',
public pointHoverBorderWidth: number = 5,
) {}
}
/**
* TooltipParams holds tooltip's configuration
*/
export class TooltipParams {
public constructor(
public tooltipModel: TooltipModel,
public tooltipModel: TooltipModel<ChartType>,
public chartId: string,
public tooltipId: string,
public markUp: string,
@ -73,7 +33,7 @@ class StylingConstants {
*/
class Styling {
public constructor(
public tooltipModel: TooltipModel,
public tooltipModel: TooltipModel<ChartType>,
public element: HTMLElement,
public topPosition: number,
public leftPosition: number,
@ -81,122 +41,6 @@ class Styling {
) {}
}
/**
* Color is a color definition.
*/
export type Color = string
/**
* TooltipItem contains datapoint information.
*/
export interface TooltipItem {
// Label for the tooltip
label: string,
// Value for the tooltip
value: string,
// X Value of the tooltip
// (deprecated) use `value` or `label` instead
xLabel: number | string,
// Y value of the tooltip
// (deprecated) use `value` or `label` instead
yLabel: number | string,
// Index of the dataset the item comes from
datasetIndex: number,
// Index of this data item in the dataset
index: number,
// X position of matching point
x: number,
// Y position of matching point
y: number
}
/**
* TooltipModel contains parameters that can be used to render the tooltip.
*/
export interface TooltipModel {
// The items that we are rendering in the tooltip. See Tooltip Item Interface section
dataPoints: TooltipItem[],
// Positioning
xPadding: number,
yPadding: number,
xAlign: string,
yAlign: string,
// X and Y properties are the top left of the tooltip
x: number,
y: number,
width: number,
height: number,
// Where the tooltip points to
caretX: number,
caretY: number,
// Body
// The body lines that need to be rendered
// Each object contains 3 parameters
// before: string[] // lines of text before the line with the color square
// lines: string[], // lines of text to render as the main item with color square
// after: string[], // lines of text to render after the main lines
body: {before: string[]; lines: string[], after: string[]}[],
// lines of text that appear after the title but before the body
beforeBody: string[],
// line of text that appear after the body and before the footer
afterBody: string[],
bodyFontColor: Color,
_bodyFontFamily: string,
_bodyFontStyle: string,
_bodyAlign: string,
bodyFontSize: number,
bodySpacing: number,
// Title
// lines of text that form the title
title: string[],
titleFontColor: Color,
_titleFontFamily: string,
_titleFontStyle: string,
titleFontSize: number,
_titleAlign: string,
titleSpacing: number,
titleMarginBottom: number,
// Footer
// lines of text that form the footer
footer: string[],
footerFontColor: Color,
_footerFontFamily: string,
_footerFontStyle: string,
footerFontSize: number,
_footerAlign: string,
footerSpacing: number,
footerMarginTop: number,
// Appearance
caretSize: number,
caretPadding: number,
cornerRadius: number,
backgroundColor: Color,
// colors to render for each item in body[]. This is the color of the squares in the tooltip
labelColors: Color[],
labelTextColors: Color[],
// 0 opacity is a hidden tooltip
opacity: number,
legendColorBackground: Color,
displayColors: boolean,
borderColor: Color,
borderWidth: number
}
/**
* Tooltip provides custom tooltip rendering
*/
@ -268,12 +112,3 @@ export class ChartTooltipData {
this.value = `${size.formattedBytes} ${size.label}`;
}
}
/**
* RenderChart contains definition for renderChart and addPlugin, that can be used to cast
* a derived chart type, with `(this as unknown as RenderChart).renderChart`
*/
export interface RenderChart {
renderChart<A, B>(A, B): void
addPlugin (plugin?: Record<string, (chart) => void>): void
}