86 lines
3.4 KiB
Vue
86 lines
3.4 KiB
Vue
<script setup>
|
|
// Advanced tooltip component (original implementation) inspired by external patterns.
|
|
import { computed } from 'vue';
|
|
const props = defineProps({
|
|
hideLabel: { type: Boolean, default: false },
|
|
hideIndicator: { type: Boolean, default: false },
|
|
indicator: { type: String, default: 'dot' }, // 'dot' | 'line' | 'dashed'
|
|
nameKey: { type: String, required: false },
|
|
labelKey: { type: String, required: false },
|
|
labelFormatter: { type: Function, required: false },
|
|
payload: { type: Object, required: false, default: () => ({}) },
|
|
config: { type: Object, required: false, default: () => ({}) },
|
|
class: { type: [String, Array, Object], required: false },
|
|
color: { type: String, required: false },
|
|
x: { type: [Number, Date, String], required: false },
|
|
});
|
|
|
|
// Build array of entries referencing config for label & color
|
|
const entries = computed(() => {
|
|
return Object.entries(props.payload)
|
|
.map(([key, value]) => {
|
|
const seriesKey = props.nameKey || key;
|
|
const itemConfig = props.config[seriesKey] || props.config[key] || {};
|
|
const indicatorColor = itemConfig.color || props.color;
|
|
return { key, value, itemConfig, indicatorColor };
|
|
})
|
|
.filter(e => e.itemConfig && (e.itemConfig.label || e.value !== undefined));
|
|
});
|
|
|
|
const singleSeries = computed(() => entries.value.length === 1 && props.indicator !== 'dot');
|
|
|
|
const formattedLabel = computed(() => {
|
|
if (props.hideLabel) return null;
|
|
if (props.labelFormatter && props.x !== undefined) {
|
|
return props.labelFormatter(props.x);
|
|
}
|
|
if (props.labelKey) {
|
|
const cfg = props.config[props.labelKey];
|
|
return cfg?.label || props.payload[props.labelKey];
|
|
}
|
|
return props.x instanceof Date ? props.x.toLocaleDateString() : props.x;
|
|
});
|
|
|
|
function formatValue(v) {
|
|
if (v == null) return '';
|
|
if (typeof v === 'number') return v.toLocaleString();
|
|
return v;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div :class="['border border-border/50 bg-background min-w-32 rounded-lg px-2.5 py-1.5 text-xs shadow-xl', props.class]">
|
|
<div v-if="!singleSeries && formattedLabel" class="font-medium mb-1">{{ formattedLabel }}</div>
|
|
<div class="grid gap-1.5">
|
|
<div
|
|
v-for="{ key, value, itemConfig, indicatorColor } in entries"
|
|
:key="key"
|
|
class="flex w-full flex-wrap items-stretch gap-2"
|
|
:class="indicator === 'dot' ? 'items-center' : 'items-start'"
|
|
>
|
|
<!-- Indicator -->
|
|
<template v-if="!hideIndicator">
|
|
<div
|
|
:class="[
|
|
'shrink-0 rounded-[2px] border-border',
|
|
indicator === 'dot' && 'h-2.5 w-2.5',
|
|
indicator === 'line' && 'w-1 h-4',
|
|
indicator === 'dashed' && 'w-0 h-4 border-[1.5px] border-dashed bg-transparent',
|
|
singleSeries && indicator === 'dashed' && 'my-0.5'
|
|
]"
|
|
:style="{ '--color-bg': indicatorColor, '--color-border': indicatorColor }"
|
|
/>
|
|
</template>
|
|
|
|
<div :class="['flex flex-1 justify-between leading-none', singleSeries ? 'items-end' : 'items-center']">
|
|
<div class="grid gap-1.5">
|
|
<div v-if="singleSeries && formattedLabel" class="font-medium">{{ formattedLabel }}</div>
|
|
<span class="text-muted-foreground">{{ itemConfig.label || formatValue(value) }}</span>
|
|
</div>
|
|
<span v-if="value !== undefined" class="font-mono font-medium tabular-nums">{{ formatValue(value) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|