feat(lite/component): Linear Chart (#6376)

This commit is contained in:
Thierry Goettelmann 2022-09-08 10:52:36 +02:00 committed by Julien Fontanet
parent 41f5634b7a
commit aebb47ad38
12 changed files with 635 additions and 4 deletions

View File

@ -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"
},

View File

@ -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();
</script>
<style lang="postcss">
@ -55,14 +58,14 @@ watchEffect(() => {
max-width: 37rem;
height: calc(100vh - 9rem);
padding: 0.5rem;
background-color: var(--background-color-primary);
border-right: 1px solid var(--color-blue-scale-400);
background-color: var(--background-color-primary);
}
.main {
flex: 1;
background-color: var(--background-color-secondary);
height: calc(100vh - 9rem);
overflow: auto;
flex: 1;
height: calc(100vh - 9rem);
background-color: var(--background-color-secondary);
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<div class="chart-summary">
<div>
<div class="label">{{ $t("total-used") }}</div>
<div>
{{ usedPercent }}%
<br />
{{ valueFormatter(used) }}
</div>
</div>
<div>
<div class="label">{{ $t("total-free") }}</div>
<div>
{{ freePercent }}%
<br />
{{ valueFormatter(total - used) }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, inject } from "vue";
import { percent } from "@/libs/utils";
const props = defineProps<{
total: number;
used: number;
}>();
const usedPercent = computed(() => percent(props.used, props.total));
const freePercent = computed(() =>
percent(props.total - props.used, props.total)
);
const valueFormatter = inject("valueFormatter") as (value: number) => string;
</script>
<style lang="postcss" scoped>
.chart-summary {
font-size: 1.4rem;
font-weight: 700;
display: flex;
margin-top: 2rem;
color: var(--color-blue-scale-200);
gap: 4rem;
& > div {
display: flex;
flex: 1;
justify-content: space-between;
}
}
.label {
text-transform: uppercase;
}
</style>

View File

@ -0,0 +1,38 @@
# LinearChart component
```vue
<template>
<LinearChart
title="Chart title"
subtitle="Chart subtitle"
:data="data"
:value-formatter="customValueFormatter"
/>
</template>
<script lang="ts" setup>
import type { LinearChartData } from "@/types/chart";
import LinearChart from "@/components/charts/LinearChart.vue";
const data: LinearChartData = [
{
label: "First series",
data: [
{ date: "...", value: 1234 },
{ date: "...", value: 1234 },
],
},
{
label: "Second series",
data: [
{ date: "...", value: 1234 },
{ date: "...", value: 1234 },
],
},
];
const customValueFormatter = (value: number) => {
return `${value} (Doubled: ${value * 2})`;
};
</script>
```

View File

@ -0,0 +1,89 @@
<template>
<UiCard class="linear-chart">
<VueCharts :option="option" autoresize class="chart" />
<slot name="summary" />
</UiCard>
</template>
<script lang="ts" setup>
import type { EChartsOption } from "echarts";
import { computed, provide } from "vue";
import VueCharts from "vue-echarts";
import type { LinearChartData } from "@/types/chart";
import { LineChart } from "echarts/charts";
import {
GridComponent,
LegendComponent,
TitleComponent,
TooltipComponent,
} from "echarts/components";
import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import type { OptionDataValue } from "echarts/types/src/util/types";
import UiCard from "@/components/ui/UiCard.vue";
const props = defineProps<{
title?: string;
subtitle?: string;
data: LinearChartData;
valueFormatter?: (value: number) => string;
}>();
const valueFormatter = (value: OptionDataValue | OptionDataValue[]) => {
if (props.valueFormatter) {
return props.valueFormatter(value as number);
}
return value.toString();
};
provide("valueFormatter", valueFormatter);
use([
CanvasRenderer,
LineChart,
GridComponent,
TooltipComponent,
TitleComponent,
LegendComponent,
]);
const option = computed<EChartsOption>(() => ({
title: {
text: props.title,
subtext: props.subtitle,
},
legend: {
data: props.data.map((series) => series.label),
},
tooltip: {
valueFormatter,
},
xAxis: {
type: "time",
axisLabel: {
showMinLabel: true,
showMaxLabel: true,
},
},
yAxis: {
type: "value",
axisLabel: {
formatter: valueFormatter,
},
},
series: props.data.map((series, index) => ({
type: "line",
name: series.label,
zlevel: index + 1,
data: series.data.map((item) => [item.date, item.value]),
})),
}));
</script>
<style lang="postcss" scoped>
.chart {
width: 50rem;
height: 30rem;
}
</style>

View File

@ -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",
},
},
},
});
};

View File

@ -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);
}

View File

@ -31,5 +31,7 @@
"switch-theme": "Switch theme",
"system": "System",
"tasks": "Tasks",
"total-free": "Total free",
"total-used": "Total used",
"vms": "VMs"
}

View File

@ -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"
}

View File

@ -0,0 +1,7 @@
export type LinearChartData = {
label: string;
data: {
date: string;
value: number;
}[];
}[];

View File

@ -0,0 +1,3 @@
declare module "human-format" {
function bytes(value: number): string;
}

View File

@ -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"