在数据大屏项目中需要完成一个韦恩图,首先想到的是antv/g2中venn,后来正好在看svg相关的文章,我就在想是不是可以使用svg实现呢。
由于数据大屏需要考虑不同的屏幕尺寸,在画svg的时候需要适应容器的宽高,所以需要获取容器的宽高然后赋值给svg,并且为了保证居中效果,每个元素在定位的时候都是基于(svgWidth/2,svgHeight/2)为中心点。
<template>
<div class="align-center flex h-full w-full justify-center" ref="containerRef">
<svg ref="svgRef" :width="svgWidth" :height="svgHeight">
<!-- 定义线性渐变 -->
<defs>
<linearGradient id="grad1" x1="50%" y1="50%" x2="75%" y2="100%">
<stop offset="0%" style="stop-color: #3b82f6; stop-opacity: 1" />
<stop offset="100%" style="stop-color: #3b82f6; stop-opacity: 0.3" />
</linearGradient>
</defs>
<defs>
<linearGradient id="grad2" x1="75%" y1="50%" x2="50%" y2="100%">
<stop offset="0%" style="stop-color: #ef4444; stop-opacity: 1" />
<stop offset="100%" style="stop-color: #ef4444; stop-opacity: 0.3" />
</linearGradient>
</defs>
<defs>
<linearGradient id="grad3" x1="100%" y1="50%" x2="100%" y2="25%">
<stop offset="0%" style="stop-color: #f59e0b; stop-opacity: 1" />
<stop offset="100%" style="stop-color: #f59e0b; stop-opacity: 0.3" />
</linearGradient>
</defs>
<!-- 绘制维恩图的各个椭圆 -->
<g>
<!-- B - 蓝色椭圆 -->
<ellipse
:cx="getSize(svgWidth / 2 - svgWidth / 3 / 2 + 2)"
:cy="getSize(svgHeight / 2 - svgHeight / 3 / 2 + 2)"
:rx="getSize(svgWidth / 3)"
:ry="getSize(svgHeight / 3)"
fill="#ffffff"
fill-opacity="0.3"
stroke="url(#grad1)"
stroke-width="2"
/>
<!-- A - 红色椭圆 -->
<ellipse
:cx="getSize(svgWidth / 2 + svgWidth / 3 / 2 - 2)"
:cy="getSize(svgHeight / 2 - svgHeight / 3 / 2 + 2)"
:rx="getSize(svgWidth / 3)"
:ry="getSize(svgHeight / 3)"
fill="#ffffff"
fill-opacity="0.3"
stroke="url(#grad2)"
stroke-width="2"
/>
<!-- D - 橙色椭圆 -->
<ellipse
:cx="getSize(svgWidth / 2)"
:cy="getSize(svgHeight / 2 + svgHeight / 3 / 2 - 2)"
:rx="getSize(svgWidth / 3)"
:ry="getSize(svgHeight / 3)"
fill="#ffffff"
fill-opacity="0.3"
stroke="url(#grad3)"
stroke-width="2"
/>
</g>
<!-- 绘制标签和数据 -->
<text :x="getSize(svgWidth / 2 - svgWidth / 3 / 2)" :y="getSize(svgHeight / 2 - svgHeight / 3)" text-anchor="end">
<tspan :font-size="setSize(12)">B独家</tspan>
<tspan class="cursor-pointer" fill="#3b82f6" :font-size="setSize(14)" @click="click('B')">1781.4万</tspan>
</text>
<text
:x="getSize(svgWidth / 2 - svgWidth / 3 / 2)"
:y="getSize(svgHeight / 2 - svgHeight / 3 + setSize(14) + 4)"
text-anchor="end"
>
<tspan class="font-bold" :font-size="setSize(16)">B</tspan>
</text>
<line
:x1="getSize(svgWidth / 2 - svgWidth / 3 / 2)"
:y1="getSize(svgHeight / 2 - svgHeight / 3 + setSize(14) + 8)"
:x2="getSize(svgWidth / 2 - svgWidth / 3 / 2 - setSize(80))"
:y2="getSize(svgHeight / 2 - svgHeight / 3 + setSize(14) + 8)"
stroke="#3b82f6"
stroke-width="2"
/>
<text
:x="getSize(svgWidth / 2 - svgWidth / 3 / 2)"
:y="getSize(svgHeight / 2 - svgHeight / 3 + setSize(14) + setSize(16) + 12)"
text-anchor="end"
>
<tspan class="font-bold" fill="#3b82f6" :font-size="setSize(16)">17752.7万</tspan>
</text>
<text :x="getSize(svgWidth / 2 + svgWidth / 3 / 2 - 2)" :y="getSize(svgHeight / 2 - svgHeight / 3)">
<tspan :font-size="setSize(12)">A独家</tspan>
<tspan fill="#3b82f6" :font-size="setSize(14)">1781.4万</tspan>
</text>
<text :x="getSize(svgWidth / 2 + svgWidth / 3 / 2 - 2)" :y="getSize(svgHeight / 2 - svgHeight / 3 + setSize(14) + 4)">
<tspan class="font-bold" :font-size="setSize(16)">A</tspan>
</text>
<line
:x1="getSize(svgWidth / 2 + svgWidth / 3 / 2 - 2)"
:y1="getSize(svgHeight / 2 - svgHeight / 3 + setSize(14) + 8)"
:x2="getSize(svgWidth / 2 + svgWidth / 3 / 2 - 2 + setSize(80))"
:y2="getSize(svgHeight / 2 - svgHeight / 3 + setSize(14) + 8)"
stroke="#3b82f6"
stroke-width="2"
/>
<text
:x="getSize(svgWidth / 2 + svgWidth / 3 / 2 - 2)"
:y="getSize(svgHeight / 2 - svgHeight / 3 + setSize(14) + setSize(16) + 12)"
>
<tspan class="font-bold" fill="#3b82f6" :font-size="setSize(16)">17752.7万</tspan>
</text>
<text :x="getSize(svgWidth / 2)" :y="getSize(svgHeight / 2 + svgHeight / 3 / 2 + 10)" text-anchor="middle">
<tspan :font-size="setSize(12)">D独家</tspan>
<tspan fill="#3b82f6" :font-size="setSize(14)">1781.4万</tspan>
</text>
<text
:x="getSize(svgWidth / 2)"
:y="getSize(svgHeight / 2 + svgHeight / 3 / 2 + 10 + setSize(14) + 4)"
text-anchor="middle"
>
<tspan class="font-bold" :font-size="setSize(16)">D</tspan>
</text>
<line
:x1="getSize(svgWidth / 2 - setSize(40))"
:y1="getSize(svgHeight / 2 + svgHeight / 3 / 2 + 10 + setSize(14) + 8)"
:x2="getSize(svgWidth / 2 + setSize(40))"
:y2="getSize(svgHeight / 2 + svgHeight / 3 / 2 + 10 + setSize(14) + 8)"
stroke="#3b82f6"
stroke-width="2"
/>
<text
:x="getSize(svgWidth / 2)"
:y="getSize(svgHeight / 2 + svgHeight / 3 / 2 + 10 + setSize(14) + setSize(16) + 12)"
text-anchor="middle"
>
<tspan class="font-bold" fill="#3b82f6" :font-size="setSize(16)">17752.7万</tspan>
</text>
<text :x="getSize(svgWidth / 2)" :y="getSize(svgHeight / 2)">
<tspan fill="#3b82f6" text-anchor="middle" :font-size="setSize(12)">B&A&D</tspan>
</text>
<text :x="getSize(svgWidth / 2)" :y="getSize(svgHeight / 2 - (svgHeight / 3 / 4) * 3)">
<tspan fill="#3b82f6" text-anchor="middle" :font-size="setSize(12)">B&A</tspan>
</text>
<text :x="getSize(svgWidth / 2 + svgWidth / 3 / 2)" :y="getSize(svgHeight / 2 + svgHeight / 3 / 4)">
<tspan fill="#3b82f6" text-anchor="middle" :font-size="setSize(12)">A&D</tspan>
</text>
<text :x="getSize(svgWidth / 2 - svgWidth / 3 / 2)" :y="getSize(svgHeight / 2 + svgHeight / 3 / 4)">
<tspan fill="#3b82f6" text-anchor="middle" :font-size="setSize(12)">B&D</tspan>
</text>
</svg>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { setSize } from "@/utils/index.js";
const containerRef = ref(null);
const svgWidth = ref(0);
const svgHeight = ref(0);
onMounted(() => {
if (containerRef.value) {
const container = containerRef.value;
const { width, height } = container.getBoundingClientRect();
svgWidth.value = Number(width).toFixed();
svgHeight.value = Number(height).toFixed();
}
});
const getSize = val => {
return Number(val).toFixed();
};
const click = val => {
console.log(val);
};
</script>