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", "@hcaptcha/vue-hcaptcha": "0.3.2",
"aws-sdk": "2.1128.0", "aws-sdk": "2.1128.0",
"bip39": "3.0.4", "bip39": "3.0.4",
"chart.js": "2.9.4", "chart.js": "4.2.1",
"core-js": "3.22.4", "core-js": "3.22.4",
"graphql": "15.3.0", "graphql": "15.3.0",
"load-script": "1.0.0", "load-script": "1.0.0",
@ -24,7 +24,6 @@
"stripe": "8.215.0", "stripe": "8.215.0",
"util": "0.12.4", "util": "0.12.4",
"vue": "2.7.10", "vue": "2.7.10",
"vue-chartjs": "3.5.1",
"vue-class-component": "7.2.6", "vue-class-component": "7.2.6",
"vue-clipboard2": "0.3.3", "vue-clipboard2": "0.3.3",
"vue-fragment": "1.6.0", "vue-fragment": "1.6.0",
@ -35,7 +34,6 @@
"vuex": "3.6.2" "vuex": "3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@types/chart.js": "2.9.36",
"@types/filesystem": "0.0.32", "@types/filesystem": "0.0.32",
"@types/jest": "27.5.0", "@types/jest": "27.5.0",
"@types/node": "16.18.14", "@types/node": "16.18.14",
@ -2705,6 +2703,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@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": { "node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@ -2972,14 +2975,6 @@
"@types/node": "*" "@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": { "node_modules/@types/connect": {
"version": "3.4.35", "version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
@ -5605,29 +5600,14 @@
} }
}, },
"node_modules/chart.js": { "node_modules/chart.js": {
"version": "2.9.4", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz",
"integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", "integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==",
"dependencies": { "dependencies": {
"chartjs-color": "^2.1.0", "@kurkle/color": "^0.3.0"
"moment": "^2.10.2"
}
}, },
"node_modules/chartjs-color": { "engines": {
"version": "2.4.1", "pnpm": "^7.0.0"
"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"
} }
}, },
"node_modules/chokidar": { "node_modules/chokidar": {
@ -5945,6 +5925,7 @@
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": { "dependencies": {
"color-name": "1.1.3" "color-name": "1.1.3"
} }
@ -5952,7 +5933,8 @@
"node_modules/color-convert/node_modules/color-name": { "node_modules/color-convert/node_modules/color-name": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "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": { "node_modules/color-name": {
"version": "1.1.4", "version": "1.1.4",
@ -13417,14 +13399,6 @@
"integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==", "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==",
"dev": true "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": { "node_modules/mrmime": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
@ -17784,21 +17758,6 @@
"csstype": "^3.1.0" "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": { "node_modules/vue-class-component": {
"version": "7.2.6", "version": "7.2.6",
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz", "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" "@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": { "@leichtgewicht/ip-codec": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@ -21243,14 +21207,6 @@
"@types/node": "*" "@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": { "@types/connect": {
"version": "3.4.35", "version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
@ -23258,29 +23214,11 @@
"dev": true "dev": true
}, },
"chart.js": { "chart.js": {
"version": "2.9.4", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz",
"integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", "integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==",
"requires": { "requires": {
"chartjs-color": "^2.1.0", "@kurkle/color": "^0.3.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"
} }
}, },
"chokidar": { "chokidar": {
@ -23522,6 +23460,7 @@
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": { "requires": {
"color-name": "1.1.3" "color-name": "1.1.3"
}, },
@ -23529,7 +23468,8 @@
"color-name": { "color-name": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "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==", "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==",
"dev": true "dev": true
}, },
"moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
},
"mrmime": { "mrmime": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
@ -32329,14 +32264,6 @@
"csstype": "^3.1.0" "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": { "vue-class-component": {
"version": "7.2.6", "version": "7.2.6",
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz", "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", "@hcaptcha/vue-hcaptcha": "0.3.2",
"aws-sdk": "2.1128.0", "aws-sdk": "2.1128.0",
"bip39": "3.0.4", "bip39": "3.0.4",
"chart.js": "2.9.4", "chart.js": "4.2.1",
"core-js": "3.22.4", "core-js": "3.22.4",
"graphql": "15.3.0", "graphql": "15.3.0",
"load-script": "1.0.0", "load-script": "1.0.0",
@ -29,7 +29,6 @@
"stripe": "8.215.0", "stripe": "8.215.0",
"util": "0.12.4", "util": "0.12.4",
"vue": "2.7.10", "vue": "2.7.10",
"vue-chartjs": "3.5.1",
"vue-class-component": "7.2.6", "vue-class-component": "7.2.6",
"vue-clipboard2": "0.3.3", "vue-clipboard2": "0.3.3",
"vue-fragment": "1.6.0", "vue-fragment": "1.6.0",
@ -40,7 +39,6 @@
"vuex": "3.6.2" "vuex": "3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@types/chart.js": "2.9.36",
"@types/filesystem": "0.0.32", "@types/filesystem": "0.0.32",
"@types/jest": "27.5.0", "@types/jest": "27.5.0",
"@types/node": "16.18.14", "@types/node": "16.18.14",

View File

@ -222,7 +222,7 @@ onMounted(() => {
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: start; align-items: flex-start;
justify-content: center; justify-content: center;
gap: 10px; gap: 10px;
} }
@ -243,12 +243,12 @@ onMounted(() => {
&__actions { &__actions {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: end; justify-content: flex-end;
gap: 5px; gap: 5px;
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
width: 100%; 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. // See LICENSE for copying information.
<script lang="ts"> <template>
import { Line } from 'vue-chartjs'; <canvas :id="chartId" :width="width" :height="height" />
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; </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 { const afterDatasetsDrawPlugin = computed((): Plugin => {
public constructor( return {
public day: string, id: 'afterDatasetsDraw',
public daysArray: string[], afterDatasetsDraw: (chart) => {
) {} if (chart.tooltip) {
const activePoint = chart.tooltip.getActiveElements();
public countMiddleDateValue(): number { if (activePoint[0]) {
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 ctx = chart.ctx; const ctx = chart.ctx;
const y_axis = chart.scales['y-axis-0']; const yAxis = chart.scales['y'];
const tooltipPosition = activePoint.tooltipPosition(); const tooltipPosition = activePoint[0].element.tooltipPosition(true);
ctx.save(); ctx.save();
ctx.beginPath(); ctx.beginPath();
ctx.setLineDash([8, 5]); ctx.setLineDash([8, 5]);
ctx.moveTo(tooltipPosition.x, tooltipPosition.y + 12); ctx.moveTo(tooltipPosition.x, tooltipPosition.y + 12);
ctx.lineTo(tooltipPosition.x, y_axis.bottom); ctx.lineTo(tooltipPosition.x, yAxis.bottom);
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.strokeStyle = '#C8D3DE'; ctx.strokeStyle = '#C8D3DE';
ctx.stroke(); ctx.stroke();
ctx.restore(); 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. * Returns chart options.
*/ */
public get chartOptions(): Record<string, unknown> { const chartOptions = computed((): ChartOptions => {
const filterCallback = this.filterDaysDisplayed;
return { return {
responsive: false, responsive: false,
maintainAspectRatios: false, maintainAspectRatio: false,
animation: false, animation: false,
hover: { clip: false,
animationDuration: 0,
},
responsiveAnimationDuration: 0,
legend: {
display: false,
},
layout: { layout: {
padding: { padding: {
top: 40, top: 20,
left: 10,
right: 10,
}, },
}, },
elements: { elements: {
point: {
radius: this.chartData.labels.length === 1 ? 10 : 0,
hoverRadius: 10,
hitRadius: 8,
},
line: { line: {
tension: 0, tension: 0,
}, },
}, },
scales: { scales: {
yAxes: [{ y: {
type: 'linear',
display: true, display: true,
ticks: { border: {
callback: function(value, _, ticks) { display: false,
const numDigits = ticks[ticks.length - 2].toString().length; },
grid: {
const power = Math.floor((numDigits - 1) / 3); display: false,
const result = value / Math.pow(1000, power);
return result;
}, },
suggestedMin: 0, suggestedMin: 0,
suggestedMax: 150, 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, display: false,
}, },
}],
xAxes: [{
display: true,
ticks: { ticks: {
fontFamily: 'font_regular', font: {
autoSkip: false, family: 'sans-serif',
},
autoSkip: true,
maxRotation: 0, maxRotation: 0,
minRotation: 0, minRotation: 0,
callback: filterCallback,
}, },
gridLines: { },
},
plugins: {
legend: {
display: false, display: false,
}, },
}], tooltip: {
},
tooltips: {
enabled: false, enabled: false,
axis: 'x', external: (context) => {
custom: (tooltipModel) => { props.tooltipConstructor(context.tooltip);
this.tooltipConstructor(tooltipModel); },
}, },
}, },
}; };
} });
/** onMounted(() => {
* Used as callback to filter days displayed on chart. chart.value = new ChartJS(
*/ document.getElementById(props.chartId) as HTMLCanvasElement,
private filterDaysDisplayed(day: string, _dayIndex: string, labelArray: string[]): string | undefined { {
const eighthDayOfTheMonth = 8; type: 'line',
const isBeforeEighthDayOfTheMonth = labelArray.length <= eighthDayOfTheMonth; data: props.chartData,
const dayShowingConditions = new DayShowingConditions(day, labelArray); options: chartOptions.value,
plugins: [afterDatasetsDrawPlugin.value],
},
);
});
if (isBeforeEighthDayOfTheMonth || this.areDaysShownOnEvenDaysAmount(dayShowingConditions) onUnmounted(() => {
|| this.areDaysShownOnNotEvenDaysAmount(dayShowingConditions)) { chart.value?.destroy();
return day; });
}
}
/** watch(() => props.chartData, () => {
* Indicates if days are shown on even days amount. chart.value?.destroy();
*/ chart.value = new ChartJS(
private areDaysShownOnEvenDaysAmount(dayShowingConditions: DayShowingConditions): boolean { document.getElementById(props.chartId) as HTMLCanvasElement,
const isDaysAmountEven = dayShowingConditions.daysArray.length % 2 === 0; {
const isDateValueInMiddleInEvenAmount = dayShowingConditions.day === type: 'line',
dayShowingConditions.daysArray[dayShowingConditions.countMiddleDateValue() - 1]; data: props.chartData,
options: chartOptions.value,
return dayShowingConditions.isDayFirstOrLast() || (isDaysAmountEven plugins: [afterDatasetsDrawPlugin.value],
&& 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);
}
}
</script> </script>

View File

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

View File

@ -3,8 +3,8 @@
<template> <template>
<VChart <VChart
id="storage-chart"
:key="chartKey" :key="chartKey"
chart-id="storage-chart"
:chart-data="chartData" :chart-data="chartData"
:width="width" :width="width"
:height="height" :height="height"
@ -14,8 +14,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue'; 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 { DataStamp } from '@/types/projects';
import { ChartUtils } from '@/utils/chart'; import { ChartUtils } from '@/utils/chart';
@ -44,19 +45,27 @@ const chartData = computed((): ChartData => {
const data: number[] = props.data.map(el => el.value); const data: number[] = props.data.map(el => el.value);
const xAxisDateLabels: string[] = ChartUtils.daysDisplayedOnChart(props.since, props.before); const xAxisDateLabels: string[] = ChartUtils.daysDisplayedOnChart(props.since, props.before);
return new ChartData( return {
xAxisDateLabels, labels: xAxisDateLabels,
'#E6EDF7', datasets: [{
'#D7E8FF',
'#003DC1',
data, 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. * 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', const tooltipParams = new TooltipParams(tooltipModel, 'storage-chart', 'storage-tooltip',
tooltipMarkUp(tooltipModel), 76, 81); tooltipMarkUp(tooltipModel), 76, 81);
@ -66,12 +75,12 @@ function tooltip(tooltipModel: TooltipModel): void {
/** /**
* Returns tooltip's html mark up. * Returns tooltip's html mark up.
*/ */
function tooltipMarkUp(tooltipModel: TooltipModel): string { function tooltipMarkUp(tooltipModel: TooltipModel<ChartType>): string {
if (!tooltipModel.dataPoints) { if (!tooltipModel.dataPoints) {
return ''; return '';
} }
const dataIndex = tooltipModel.dataPoints[0].index; const dataIndex = tooltipModel.dataPoints[0].dataIndex;
const dataPoint = new ChartTooltipData(props.data[dataIndex]); const dataPoint = new ChartTooltipData(props.data[dataIndex]);
return `<div class='tooltip'> return `<div class='tooltip'>

View File

@ -1,57 +1,17 @@
// Copyright (C) 2021 Storj Labs, Inc. // Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import { TooltipModel, ChartType } from 'chart.js';
import { DataStamp } from '@/types/projects'; import { DataStamp } from '@/types/projects';
import { Size } from '@/utils/bytesSize'; 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 * TooltipParams holds tooltip's configuration
*/ */
export class TooltipParams { export class TooltipParams {
public constructor( public constructor(
public tooltipModel: TooltipModel, public tooltipModel: TooltipModel<ChartType>,
public chartId: string, public chartId: string,
public tooltipId: string, public tooltipId: string,
public markUp: string, public markUp: string,
@ -73,7 +33,7 @@ class StylingConstants {
*/ */
class Styling { class Styling {
public constructor( public constructor(
public tooltipModel: TooltipModel, public tooltipModel: TooltipModel<ChartType>,
public element: HTMLElement, public element: HTMLElement,
public topPosition: number, public topPosition: number,
public leftPosition: 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 * Tooltip provides custom tooltip rendering
*/ */
@ -268,12 +112,3 @@ export class ChartTooltipData {
this.value = `${size.formattedBytes} ${size.label}`; 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
}