187 lines
4.0 KiB
Vue
187 lines
4.0 KiB
Vue
<template>
|
|
<LoadingCard :loading="loading" class="px-6 py-4">
|
|
<h3 class="h-6 flex mb-3 text-sm font-bold">
|
|
{{ title }}
|
|
|
|
<span class="ml-auto font-semibold text-gray-400 text-xs"
|
|
>({{ formattedTotal }} {{ __('total') }})</span
|
|
>
|
|
</h3>
|
|
|
|
<HelpTextTooltip :text="helpText" :width="helpWidth" />
|
|
|
|
<div class="flex min-h-[90px]">
|
|
<div
|
|
class="flex-1 overflow-hidden overflow-y-auto"
|
|
:class="{
|
|
'max-h-[90px]': legendsHeight === 'fixed',
|
|
}"
|
|
>
|
|
<ul>
|
|
<li
|
|
v-for="item in formattedItems"
|
|
:key="item.color"
|
|
class="text-xs leading-normal"
|
|
>
|
|
<span
|
|
class="inline-block rounded-full w-2 h-2 mr-2"
|
|
:style="{
|
|
backgroundColor: item.color,
|
|
}"
|
|
/>{{ item.label }} ({{ item.value }} - {{ item.percentage }}%)
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div
|
|
ref="chart"
|
|
class="flex-none rounded-b-lg ct-chart mr-4 w-[90px] h-[90px]"
|
|
:class="{ invisible: this.currentTotal <= 0 }"
|
|
/>
|
|
</div>
|
|
</LoadingCard>
|
|
</template>
|
|
|
|
<script>
|
|
import debounce from 'lodash/debounce'
|
|
import map from 'lodash/map'
|
|
import sumBy from 'lodash/sumBy'
|
|
import Chartist from 'chartist'
|
|
import 'chartist/dist/chartist.min.css'
|
|
|
|
const colorForIndex = index =>
|
|
[
|
|
'#F5573B',
|
|
'#F99037',
|
|
'#F2CB22',
|
|
'#8FC15D',
|
|
'#098F56',
|
|
'#47C1BF',
|
|
'#1693EB',
|
|
'#6474D7',
|
|
'#9C6ADE',
|
|
'#E471DE',
|
|
][index]
|
|
|
|
export default {
|
|
name: 'BasePartitionMetric',
|
|
|
|
props: {
|
|
loading: Boolean,
|
|
title: String,
|
|
helpText: {},
|
|
helpWidth: {},
|
|
chartData: Array,
|
|
legendsHeight: { type: String, default: 'fixed' },
|
|
},
|
|
|
|
data: () => ({
|
|
chartist: null,
|
|
resizeObserver: null,
|
|
}),
|
|
|
|
watch: {
|
|
chartData: function (newData, oldData) {
|
|
this.renderChart()
|
|
},
|
|
},
|
|
|
|
created() {
|
|
const debouncer = debounce(callback => callback(), Nova.config('debounce'))
|
|
|
|
this.resizeObserver = new ResizeObserver(entries => {
|
|
debouncer(() => {
|
|
this.renderChart()
|
|
})
|
|
})
|
|
},
|
|
|
|
mounted() {
|
|
this.chartist = new Chartist.Pie(
|
|
this.$refs.chart,
|
|
this.formattedChartData,
|
|
{
|
|
donut: true,
|
|
donutWidth: 10,
|
|
donutSolid: true,
|
|
startAngle: 270,
|
|
showLabel: false,
|
|
}
|
|
)
|
|
|
|
this.chartist.on('draw', context => {
|
|
if (context.type === 'slice') {
|
|
context.element.attr({
|
|
style: `fill: ${context.meta.color} !important`,
|
|
})
|
|
}
|
|
})
|
|
|
|
this.resizeObserver.observe(this.$refs.chart)
|
|
},
|
|
|
|
beforeUnmount() {
|
|
this.resizeObserver.unobserve(this.$refs.chart)
|
|
},
|
|
|
|
methods: {
|
|
renderChart() {
|
|
this.chartist.update(this.formattedChartData)
|
|
},
|
|
|
|
getItemColor(item, index) {
|
|
return typeof item.color === 'string' ? item.color : colorForIndex(index)
|
|
},
|
|
},
|
|
|
|
computed: {
|
|
chartClasses() {
|
|
return []
|
|
},
|
|
|
|
formattedChartData() {
|
|
return { labels: this.formattedLabels, series: this.formattedData }
|
|
},
|
|
|
|
formattedItems() {
|
|
return map(this.chartData, (item, index) => {
|
|
return {
|
|
label: item.label,
|
|
value: Nova.formatNumber(item.value),
|
|
color: this.getItemColor(item, index),
|
|
percentage: Nova.formatNumber(String(item.percentage)),
|
|
}
|
|
})
|
|
},
|
|
|
|
formattedLabels() {
|
|
return map(this.chartData, item => item.label)
|
|
},
|
|
|
|
formattedData() {
|
|
return map(this.chartData, (item, index) => {
|
|
return {
|
|
value: item.value,
|
|
meta: { color: this.getItemColor(item, index) },
|
|
}
|
|
})
|
|
},
|
|
|
|
formattedTotal() {
|
|
let total = this.currentTotal.toFixed(2)
|
|
let roundedTotal = Math.round(total)
|
|
|
|
if (roundedTotal.toFixed(2) == total) {
|
|
return Nova.formatNumber(new String(roundedTotal))
|
|
}
|
|
|
|
return Nova.formatNumber(new String(total))
|
|
},
|
|
|
|
currentTotal() {
|
|
return sumBy(this.chartData, 'value')
|
|
},
|
|
},
|
|
}
|
|
</script>
|