From aebb47ad3876dc3e8a0bffe27f675c193c1b43db Mon Sep 17 00:00:00 2001 From: Thierry Goettelmann Date: Thu, 8 Sep 2022 10:52:36 +0200 Subject: [PATCH] feat(lite/component): Linear Chart (#6376) --- @xen-orchestra/lite/package.json | 3 + @xen-orchestra/lite/src/App.vue | 11 +- .../src/components/charts/ChartSummary.vue | 59 +++ .../lite/src/components/charts/LinearChart.md | 38 ++ .../src/components/charts/LinearChart.vue | 89 ++++ .../src/composables/chart-theme.composable.ts | 382 ++++++++++++++++++ @xen-orchestra/lite/src/libs/utils.ts | 5 + @xen-orchestra/lite/src/locales/en.json | 2 + @xen-orchestra/lite/src/locales/fr.json | 2 + @xen-orchestra/lite/src/types/chart.ts | 7 + .../lite/src/types/human-format.d.ts | 3 + yarn.lock | 38 ++ 12 files changed, 635 insertions(+), 4 deletions(-) create mode 100644 @xen-orchestra/lite/src/components/charts/ChartSummary.vue create mode 100644 @xen-orchestra/lite/src/components/charts/LinearChart.md create mode 100644 @xen-orchestra/lite/src/components/charts/LinearChart.vue create mode 100644 @xen-orchestra/lite/src/composables/chart-theme.composable.ts create mode 100644 @xen-orchestra/lite/src/types/chart.ts create mode 100644 @xen-orchestra/lite/src/types/human-format.d.ts diff --git a/@xen-orchestra/lite/package.json b/@xen-orchestra/lite/package.json index d58cbe511..94c38787c 100644 --- a/@xen-orchestra/lite/package.json +++ b/@xen-orchestra/lite/package.json @@ -21,6 +21,8 @@ "complex-matcher": "^0.7.0", "d3-time-format": "^4.1.0", "decorator-synchronized": "^0.6.0", + "echarts": "^5.3.3", + "human-format": "^1.0.0", "json-rpc-2.0": "^1.3.0", "json5": "^2.2.1", "limit-concurrency-decorator": "^0.5.0", @@ -28,6 +30,7 @@ "make-error": "^1.3.6", "pinia": "^2.0.14", "vue": "^3.2.37", + "vue-echarts": "^6.2.3", "vue-i18n": "9", "vue-router": "^4.0.16" }, diff --git a/@xen-orchestra/lite/src/App.vue b/@xen-orchestra/lite/src/App.vue index 4de038fc8..471e8e0cf 100644 --- a/@xen-orchestra/lite/src/App.vue +++ b/@xen-orchestra/lite/src/App.vue @@ -21,6 +21,7 @@ import favicon from "@/assets/favicon.svg"; import AppHeader from "@/components/AppHeader.vue"; import AppLogin from "@/components/AppLogin.vue"; import InfraPoolList from "@/components/infra/InfraPoolList.vue"; +import { useChartTheme } from "@/composables/chart-theme.composable"; import { useXenApiStore } from "@/stores/xen-api.store"; let link: HTMLLinkElement | null = document.querySelector("link[rel~='icon']"); @@ -44,6 +45,8 @@ watchEffect(() => { xenApiStore.init(); } }); + +useChartTheme(); diff --git a/@xen-orchestra/lite/src/components/charts/ChartSummary.vue b/@xen-orchestra/lite/src/components/charts/ChartSummary.vue new file mode 100644 index 000000000..cb1fcbfb4 --- /dev/null +++ b/@xen-orchestra/lite/src/components/charts/ChartSummary.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/@xen-orchestra/lite/src/components/charts/LinearChart.md b/@xen-orchestra/lite/src/components/charts/LinearChart.md new file mode 100644 index 000000000..5be8ef6a7 --- /dev/null +++ b/@xen-orchestra/lite/src/components/charts/LinearChart.md @@ -0,0 +1,38 @@ +# LinearChart component + +```vue + + + +``` diff --git a/@xen-orchestra/lite/src/components/charts/LinearChart.vue b/@xen-orchestra/lite/src/components/charts/LinearChart.vue new file mode 100644 index 000000000..6a1cabfaf --- /dev/null +++ b/@xen-orchestra/lite/src/components/charts/LinearChart.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/@xen-orchestra/lite/src/composables/chart-theme.composable.ts b/@xen-orchestra/lite/src/composables/chart-theme.composable.ts new file mode 100644 index 000000000..7f719f8a8 --- /dev/null +++ b/@xen-orchestra/lite/src/composables/chart-theme.composable.ts @@ -0,0 +1,382 @@ +import { provide } from "vue"; +import { THEME_KEY } from "vue-echarts"; + +export const useChartTheme = () => { + provide(THEME_KEY, { + color: ["#8F84FF", "#EF7F18"], + backgroundColor: "#ffffff", + textStyle: {}, + grid: { + top: 80, + left: 80, + right: 20, + }, + title: { + textStyle: { + color: "#1A1B38", + fontFamily: "Poppins, sans-serif", + fontWeight: 500, + fontSize: 20, + }, + subtextStyle: { + color: "#9899A5", + fontFamily: "Poppins, sans-serif", + fontWeight: 400, + fontSize: 14, + }, + }, + line: { + itemStyle: { + borderWidth: 2, + }, + lineStyle: { + width: 2, + }, + showSymbol: false, + symbolSize: 10, + symbol: "circle", + smooth: false, + }, + radar: { + itemStyle: { + borderWidth: 2, + }, + lineStyle: { + width: 2, + }, + symbolSize: 10, + symbol: "circle", + smooth: false, + }, + bar: { + itemStyle: { + barBorderWidth: 0, + barBorderColor: "#cccccc", + }, + }, + pie: { + itemStyle: { + borderWidth: 0, + borderColor: "#cccccc", + }, + }, + scatter: { + itemStyle: { + borderWidth: 0, + borderColor: "#cccccc", + }, + }, + boxplot: { + itemStyle: { + borderWidth: 0, + borderColor: "#cccccc", + }, + }, + parallel: { + itemStyle: { + borderWidth: 0, + borderColor: "#cccccc", + }, + }, + sankey: { + itemStyle: { + borderWidth: 0, + borderColor: "#cccccc", + }, + }, + funnel: { + itemStyle: { + borderWidth: 0, + borderColor: "#cccccc", + }, + }, + gauge: { + itemStyle: { + borderWidth: 0, + borderColor: "#cccccc", + }, + }, + candlestick: { + itemStyle: { + color: "#eb8146", + color0: "transparent", + borderColor: "#d95850", + borderColor0: "#58c470", + borderWidth: "2", + }, + }, + graph: { + itemStyle: { + borderWidth: 0, + borderColor: "#cccccc", + }, + lineStyle: { + width: 1, + color: "#aaaaaa", + }, + symbolSize: "10", + symbol: "emptyArrow", + smooth: true, + color: ["#893448", "#d95850", "#eb8146", "#ffb248", "#f2d643", "#ebdba4"], + label: { + color: "#ffffff", + }, + }, + map: { + itemStyle: { + areaColor: "#f3f3f3", + borderColor: "#999999", + borderWidth: 0.5, + }, + label: { + color: "#893448", + }, + emphasis: { + itemStyle: { + areaColor: "#ffb248", + borderColor: "#eb8146", + borderWidth: 1, + }, + label: { + color: "#893448", + }, + }, + }, + geo: { + itemStyle: { + areaColor: "#f3f3f3", + borderColor: "#999999", + borderWidth: 0.5, + }, + label: { + color: "#893448", + }, + emphasis: { + itemStyle: { + areaColor: "#ffb248", + borderColor: "#eb8146", + borderWidth: 1, + }, + label: { + color: "#893448", + }, + }, + }, + categoryAxis: { + axisLine: { + show: true, + lineStyle: { + color: "#aaaaaa", + }, + }, + axisTick: { + show: false, + lineStyle: { + color: "#333", + }, + }, + axisLabel: { + show: true, + color: "#999999", + }, + splitLine: { + show: true, + lineStyle: { + color: ["#e6e6e6"], + }, + }, + splitArea: { + show: false, + areaStyle: { + color: ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"], + }, + }, + }, + valueAxis: { + axisLine: { + show: false, + // lineStyle: { + // color: "#aaaaaa", + // }, + }, + axisTick: { + show: false, + // lineStyle: { + // color: "#333", + // }, + }, + axisLabel: { + show: true, + color: "#9899A5", + }, + splitLine: { + show: true, + lineStyle: { + color: ["#E5E5E7"], + }, + }, + splitArea: { + show: false, + areaStyle: { + color: ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"], + }, + }, + }, + logAxis: { + axisLine: { + show: true, + lineStyle: { + color: "#aaaaaa", + }, + }, + axisTick: { + show: false, + lineStyle: { + color: "#333", + }, + }, + axisLabel: { + show: true, + color: "#999999", + }, + splitLine: { + show: true, + lineStyle: { + color: ["#e6e6e6"], + }, + }, + splitArea: { + show: false, + areaStyle: { + color: ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"], + }, + }, + }, + timeAxis: { + axisLine: { + show: false, + // lineStyle: { + // color: "#aaaaaa", + // }, + }, + axisTick: { + show: false, + // lineStyle: { + // color: "#333", + // }, + }, + axisLabel: { + show: true, + color: "#9899A5", + }, + splitLine: { + show: true, + lineStyle: { + type: "dashed", + color: ["#E5E5E7"], + }, + }, + splitArea: { + show: false, + areaStyle: { + color: ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"], + }, + }, + }, + toolbox: { + iconStyle: { + borderColor: "#999999", + }, + emphasis: { + iconStyle: { + borderColor: "#666666", + }, + }, + }, + legend: { + left: "right", + top: "bottom", + textStyle: { + color: "#9899A5", + }, + }, + tooltip: { + trigger: "axis", + axisPointer: { + lineStyle: { + color: "#8F84FF", + width: 1, + }, + crossStyle: { + color: "#8F84FF", + width: 1, + }, + }, + }, + timeline: { + lineStyle: { + color: "#893448", + width: 1, + }, + itemStyle: { + color: "#893448", + borderWidth: 1, + }, + controlStyle: { + color: "#893448", + borderColor: "#893448", + borderWidth: 0.5, + }, + checkpointStyle: { + color: "#eb8146", + borderColor: "#ffb248", + }, + label: { + color: "#893448", + }, + emphasis: { + itemStyle: { + color: "#ffb248", + }, + controlStyle: { + color: "#893448", + borderColor: "#893448", + borderWidth: 0.5, + }, + label: { + color: "#893448", + }, + }, + }, + visualMap: { + color: [ + "#893448", + "#d95850", + "#eb8146", + "#ffb248", + "#f2d643", + "rgb(247,238,173)", + ], + }, + dataZoom: { + backgroundColor: "rgba(255,255,255,0)", + dataBackgroundColor: "rgba(255,178,72,0.5)", + fillerColor: "rgba(255,178,72,0.15)", + handleColor: "#ffb248", + handleSize: "100%", + textStyle: { + color: "#333", + }, + }, + markPoint: { + label: { + color: "#ffffff", + }, + emphasis: { + label: { + color: "#ffffff", + }, + }, + }, + }); +}; diff --git a/@xen-orchestra/lite/src/libs/utils.ts b/@xen-orchestra/lite/src/libs/utils.ts index f5adef448..9e173ef8f 100644 --- a/@xen-orchestra/lite/src/libs/utils.ts +++ b/@xen-orchestra/lite/src/libs/utils.ts @@ -1,4 +1,5 @@ import { utcParse } from "d3-time-format"; +import { round } from "lodash-es"; import type { Filter } from "@/types/filter"; import { faSquareCheck } from "@fortawesome/free-regular-svg-icons"; import { faFont, faHashtag, faList } from "@fortawesome/free-solid-svg-icons"; @@ -52,3 +53,7 @@ export function parseDateTime(dateTime: string) { } return date.getTime(); } + +export function percent(currentValue: number, maxValue: number, precision = 2) { + return round((currentValue / maxValue) * 100, precision); +} diff --git a/@xen-orchestra/lite/src/locales/en.json b/@xen-orchestra/lite/src/locales/en.json index 314438ede..450257c57 100644 --- a/@xen-orchestra/lite/src/locales/en.json +++ b/@xen-orchestra/lite/src/locales/en.json @@ -31,5 +31,7 @@ "switch-theme": "Switch theme", "system": "System", "tasks": "Tasks", + "total-free": "Total free", + "total-used": "Total used", "vms": "VMs" } diff --git a/@xen-orchestra/lite/src/locales/fr.json b/@xen-orchestra/lite/src/locales/fr.json index 8705db2dd..092169a0e 100644 --- a/@xen-orchestra/lite/src/locales/fr.json +++ b/@xen-orchestra/lite/src/locales/fr.json @@ -31,5 +31,7 @@ "switch-theme": "Changer de thème", "system": "Système", "tasks": "Tâches", + "total-free": "Total libre", + "total-used": "Total utilisé", "vms": "VMs" } diff --git a/@xen-orchestra/lite/src/types/chart.ts b/@xen-orchestra/lite/src/types/chart.ts new file mode 100644 index 000000000..edb2b8fdc --- /dev/null +++ b/@xen-orchestra/lite/src/types/chart.ts @@ -0,0 +1,7 @@ +export type LinearChartData = { + label: string; + data: { + date: string; + value: number; + }[]; +}[]; diff --git a/@xen-orchestra/lite/src/types/human-format.d.ts b/@xen-orchestra/lite/src/types/human-format.d.ts new file mode 100644 index 000000000..c31014d4d --- /dev/null +++ b/@xen-orchestra/lite/src/types/human-format.d.ts @@ -0,0 +1,3 @@ +declare module "human-format" { + function bytes(value: number): string; +} diff --git a/yarn.lock b/yarn.lock index 348be50f6..e46775755 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7961,6 +7961,14 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +echarts@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.3.3.tgz#df97b09c4c0e2ffcdfb44acf518d50c50e0b838e" + integrity sha512-BRw2serInRwO5SIwRviZ6Xgm5Lb7irgz+sLiFMmy/HOaf4SQ+7oYqxKzRHAKp4xHQ05AuHw1xvoQWJjDQq/FGw== + dependencies: + tslib "2.3.0" + zrender "5.3.2" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -17166,6 +17174,11 @@ reselect@^2.5.4: resolved "https://registry.yarnpkg.com/reselect/-/reselect-2.5.4.tgz#b7d23fdf00b83fa7ad0279546f8dbbbd765c7047" integrity sha512-KjVKPrNEDQ4nDuIFseuoNP3yp2lz8bipFlCjUkbD8WZTt2zTcJIfCDaSdOkGrChu9nKfWeXsljQQ2j4I5pcwaQ== +resize-detector@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/resize-detector/-/resize-detector-0.3.0.tgz#fe495112e184695500a8f51e0389f15774cb1cfc" + integrity sha512-R/tCuvuOHQ8o2boRP6vgx8hXCCy87H1eY9V5imBYeVNyNVpuL9ciReSccLj2gDcax9+2weXy3bc8Vv+NRXeEvQ== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -19068,6 +19081,11 @@ tsconfig-paths@^3.11.0, tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + tslib@^1.11.1, tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -19771,6 +19789,19 @@ vue-demi@*: resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.5.tgz#d5eddbc9eaefb89ce5995269d1fa6b0486312092" integrity sha512-tO3K2bML3AwiHmVHeKCq6HLef2st4zBXIV5aEkoJl6HZ+gJWxWv2O8wLH8qrA3SX3lDoTDHNghLX1xZg83MXvw== +vue-demi@^0.13.2: + version "0.13.7" + resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.7.tgz#5ae380b15c13be556ac4a0da0a48450c98a01d4b" + integrity sha512-hbhlvpx1gFW3TB5HxJ0mNxyA9Jh5iQt409taOs6zkhpvfJ7YzLs1rsLufJmDsjH5PI1cOyfikY1fE/meyHfU5A== + +vue-echarts@^6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/vue-echarts/-/vue-echarts-6.2.3.tgz#77973c417a56bca76847576ab903ab92979d75bb" + integrity sha512-xHzUvgsgk/asJTcNa8iVVwoovZU3iEUHvmBa3bzbiP3Y6OMxM1YXsoWOKVmVVaUusGs4ob4pSwjwNy2FemAz9w== + dependencies: + resize-detector "^0.3.0" + vue-demi "^0.13.2" + vue-eslint-parser@^9.0.0, vue-eslint-parser@^9.0.1: version "9.0.3" resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz#0c17a89e0932cc94fa6a79f0726697e13bfe3c96" @@ -20654,3 +20685,10 @@ zepto@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/zepto/-/zepto-1.2.0.tgz#e127bd9e66fd846be5eab48c1394882f7c0e4f98" integrity sha512-C1x6lfvBICFTQIMgbt3JqMOno3VOtkWat/xEakLTOurskYIHPmzJrzd1e8BnmtdDVJlGuk5D+FxyCA8MPmkIyA== + +zrender@5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.3.2.tgz#f67b11d36d3d020d62411d3bb123eb1c93cccd69" + integrity sha512-8IiYdfwHj2rx0UeIGZGGU4WEVSDEdeVCaIg/fomejg1Xu6OifAL1GVzIPHg2D+MyUkbNgPWji90t0a8IDk+39w== + dependencies: + tslib "2.3.0"