Files
better-auth/examples/nuxt-example/components/ui/chart-donut/DonutChart.vue
Bereket Engida 9f2e45b8c7 chore: cleanup
2025-01-06 14:30:39 +03:00

122 lines
3.2 KiB
Vue

<script setup lang="ts" generic="T extends Record<string, any>">
import { type Component, computed, ref } from "vue";
import { useMounted } from "@vueuse/core";
import type { BaseChartProps } from ".";
import { defaultColors } from "@/components/ui/chart";
const props = withDefaults(
defineProps<
Pick<
BaseChartProps<T>,
| "data"
| "colors"
| "index"
| "margin"
| "showLegend"
| "showTooltip"
| "filterOpacity"
> & {
/**
* Sets the name of the key containing the quantitative chart values.
*/
category: KeyOfT;
/**
* Change the type of the chart
* @default "donut"
*/
type?: "donut" | "pie";
/**
* Function to sort the segment
*/
sortFunction?: (a: any, b: any) => number | undefined;
/**
* Controls the formatting for the label.
*/
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string;
/**
* Render custom tooltip component.
*/
customTooltip?: Component;
}
>(),
{
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
sortFunction: () => undefined,
valueFormatter: (tick: number) => `${tick}`,
type: "donut",
filterOpacity: 0.2,
showTooltip: true,
showLegend: true,
},
);
type KeyOfT = Extract<keyof T, string>;
type Data = (typeof props.data)[number];
const category = computed(() => props.category as KeyOfT);
const index = computed(() => props.index as KeyOfT);
const isMounted = useMounted();
const activeSegmentKey = ref<string>();
const colors = computed(() =>
props.colors?.length
? props.colors
: defaultColors(
props.data.filter((d) => d[props.category]).filter(Boolean).length,
),
);
const legendItems = computed(() =>
props.data.map((item, i) => ({
name: item[props.index],
color: colors.value[i],
inactive: false,
})),
);
const totalValue = computed(() =>
props.data.reduce((prev, curr) => {
return prev + curr[props.category];
}, 0),
);
</script>
<template>
<div :class="cn('w-full h-48 flex flex-col items-end', $attrs.class ?? '')">
<VisSingleContainer :style="{ height: isMounted ? '100%' : 'auto' }" :margin="{ left: 20, right: 20 }" :data="data">
<ChartSingleTooltip
:selector="Donut.selectors.segment"
:index="category"
:items="legendItems"
:value-formatter="valueFormatter"
:custom-tooltip="customTooltip"
/>
<VisDonut
:value="(d: Data) => d[category]"
:sort-function="sortFunction"
:color="colors"
:arc-width="type === 'donut' ? 20 : 0"
:show-background="false"
:central-label="type === 'donut' ? valueFormatter(totalValue) : ''"
:events="{
[Donut.selectors.segment]: {
click: (d: Data, ev: PointerEvent, i: number, elements: HTMLElement[]) => {
if (d?.data?.[index] === activeSegmentKey) {
activeSegmentKey = undefined
elements.forEach(el => el.style.opacity = '1')
}
else {
activeSegmentKey = d?.data?.[index]
elements.forEach(el => el.style.opacity = `${filterOpacity}`)
elements[i].style.opacity = '1'
}
},
},
}"
/>
<slot />
</VisSingleContainer>
</div>
</template>