chart
Radar chart with gradient fill
Radar chart with gradient fill
Загрузка превью…
src/chart/c-chart-23.vue
<!--
Пробел в API, не просто замена: `vue-chrts` (в отличие от recharts) не
экспортирует ни `RadarChart`, ни `PolarGrid`/`PolarAngleAxis` вовсе — в
пакете нет ни одного полярного графика (см. docs/PORTING.md §19/§27,
полный список компонентов `vue-chrts`: AreaChart/LineChart/BarChart/
DonutChart/BubbleChart/GanttChart/DagreGraph/DualChart/SankeyChart/Maps).
Заменить recharts `RadarChart` на "другой рендерер того же типа графика"
здесь невозможно — такого типа графика в vue-chrts нет вообще.
Ниже — минимальная самостоятельно нарисованная SVG-паутина (полигон по
тем же данным/углам), а не попытка сымитировать recharts алгоритм
тик-в-тик: сетка градуирована на 4 уровня, оси подписаны по `skill`.
Градиентная заливка и glow-фильтр перенесены (это чистый SVG, не завязан
на конкретную чарт-библиотеку).
-->
<script setup lang="ts">
import { computed } from "vue"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ChartContainer, type ChartConfig } from "@/components/ui/chart"
const chartData = [
{ skill: "Frontend", score: 92 },
{ skill: "Backend", score: 78 },
{ skill: "DevOps", score: 68 },
{ skill: "Design", score: 74 },
{ skill: "Testing", score: 85 },
{ skill: "Security", score: 62 },
]
const chartConfig = {
score: { label: "Proficiency", color: "var(--chart-1)" },
} satisfies ChartConfig
const SIZE = 280
const CENTER = SIZE / 2
const RADIUS = 100
const MAX = 100
const LEVELS = 4
function pointAt(index: number, total: number, fraction: number) {
const angle = (Math.PI * 2 * index) / total - Math.PI / 2
return {
x: CENTER + Math.cos(angle) * RADIUS * fraction,
y: CENTER + Math.sin(angle) * RADIUS * fraction,
}
}
const gridPolygons = computed(() =>
Array.from({ length: LEVELS }, (_, level) => {
const fraction = (level + 1) / LEVELS
return chartData
.map((_, i) => {
const p = pointAt(i, chartData.length, fraction)
return `${p.x},${p.y}`
})
.join(" ")
})
)
const spokes = computed(() =>
chartData.map((_, i) => pointAt(i, chartData.length, 1))
)
const dataPolygon = computed(() =>
chartData
.map((d, i) => {
const p = pointAt(i, chartData.length, d.score / MAX)
return `${p.x},${p.y}`
})
.join(" ")
)
const dataPoints = computed(() =>
chartData.map((d, i) => pointAt(i, chartData.length, d.score / MAX))
)
const labels = computed(() =>
chartData.map((d, i) => ({ ...pointAt(i, chartData.length, 1.18), label: d.skill }))
)
</script>
<template>
<Card class="w-full max-w-xs">
<CardHeader class="items-center pb-0">
<CardTitle>Skill Assessment</CardTitle>
<CardDescription>Team proficiency across key areas</CardDescription>
</CardHeader>
<CardContent class="flex-1 pb-0">
<ChartContainer :config="chartConfig" class="mx-auto aspect-square max-h-[280px]">
<svg :viewBox="`0 0 ${SIZE} ${SIZE}`" class="h-full w-full">
<defs>
<linearGradient id="chart23-fill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="var(--color-score)" stop-opacity="0.5" />
<stop offset="100%" stop-color="var(--color-score)" stop-opacity="0.08" />
</linearGradient>
</defs>
<polygon
v-for="(poly, i) in gridPolygons"
:key="i"
:points="poly"
fill="none"
stroke="var(--border)"
stroke-dasharray="3 3"
/>
<line
v-for="(p, i) in spokes"
:key="i"
:x1="CENTER"
:y1="CENTER"
:x2="p.x"
:y2="p.y"
stroke="var(--border)"
stroke-dasharray="3 3"
/>
<polygon :points="dataPolygon" fill="url(#chart23-fill)" stroke="var(--color-score)" stroke-width="2" />
<circle
v-for="(p, i) in dataPoints"
:key="i"
:cx="p.x"
:cy="p.y"
r="4"
fill="var(--color-score)"
/>
<text
v-for="(l, i) in labels"
:key="i"
:x="l.x"
:y="l.y"
text-anchor="middle"
dominant-baseline="middle"
font-size="12"
fill="var(--muted-foreground)"
>{{ l.label }}</text>
</svg>
</ChartContainer>
</CardContent>
</Card>
</template>