81 lines
2.6 KiB
Vue
81 lines
2.6 KiB
Vue
<template>
|
|
<div class="space-y-2">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-semibold text-gray-300">身分泳道圖 V2</h3>
|
|
<span class="text-xs text-gray-500">{{ identities.length }} identities</span>
|
|
</div>
|
|
<div class="relative overflow-x-auto" ref="scrollRef">
|
|
<svg :width="svgW" :height="rowH * identities.length + 30" class="block">
|
|
<!-- time axis -->
|
|
<line x1="80" :y1="rowH * identities.length + 5" :x2="svgW" :y2="rowH * identities.length + 5" stroke="#4b5563" stroke-width="1" />
|
|
<g v-for="t in ticks" :key="t">
|
|
<line :x1="xPos(t)" :y1="rowH * identities.length + 1" :x2="xPos(t)" :y2="rowH * identities.length + 5" stroke="#6b7280" stroke-width="1" />
|
|
<text :x="xPos(t)" :y="rowH * identities.length + 16" fill="#9ca3af" font-size="9" text-anchor="middle">{{ t }}s</text>
|
|
</g>
|
|
<!-- swimlanes -->
|
|
<g v-for="(ident, i) in identities" :key="ident.name">
|
|
<text x="4" :y="rowH * i + rowH / 2 + 5" fill="#d1d5db" font-size="11" class="select-none">{{ ident.name }}</text>
|
|
<rect x="78" :y="rowH * i + 4" width="2" :height="rowH - 8" fill="#374151" rx="2" />
|
|
<rect
|
|
v-for="seg in ident.segments" :key="seg.start"
|
|
:x="xPos(seg.start)" :y="rowH * i + 6"
|
|
:width="Math.max(2, xPos(seg.end) - xPos(seg.start))"
|
|
:height="rowH - 12"
|
|
:fill="ident.color"
|
|
:opacity="0.7"
|
|
rx="3"
|
|
class="cursor-pointer hover:opacity-100"
|
|
@click="$emit('selectTrace', seg.trace_id)"
|
|
/>
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
|
|
const props = defineProps<{
|
|
identities: SwimlaneIdentity[]
|
|
totalDuration: number
|
|
}>()
|
|
|
|
defineEmits<{ selectTrace: [traceId: number] }>()
|
|
|
|
export interface SwimlaneSegment {
|
|
trace_id: number
|
|
start: number
|
|
end: number
|
|
face_count: number
|
|
}
|
|
|
|
export interface SwimlaneIdentity {
|
|
name: string
|
|
color: string
|
|
segments: SwimlaneSegment[]
|
|
}
|
|
|
|
const rowH = 28
|
|
const labelW = 80
|
|
const padR = 20
|
|
|
|
const svgW = computed(() => {
|
|
const dur = props.totalDuration || 6000
|
|
return Math.max(500, labelW + dur / 8)
|
|
})
|
|
|
|
function xPos(sec: number): number {
|
|
const dur = props.totalDuration || 6000
|
|
return labelW + (sec / dur) * (svgW.value - labelW - padR)
|
|
}
|
|
|
|
const ticks = computed(() => {
|
|
const dur = props.totalDuration || 6000
|
|
const step = Math.max(30, Math.round(dur / 6 / 30) * 30)
|
|
const tks: number[] = []
|
|
for (let t = 0; t <= dur; t += step) tks.push(t)
|
|
return tks
|
|
})
|
|
</script>
|