svg 实现饼图

<template>
  <div class="pie-chart" @mousemove="updateTooltipPosition">
    <svg :width="size" :height="size" viewBox="-1 -1 2 2">
      <g>
        <path
          v-for="(slice, index) in slices"
          :key="index"
          :d="
            generateArc(
              slice.startAngle,
              slice.endAngle,
              highlightedIndex === index
            )
          "
          :fill="effectiveColors[index % effectiveColors.length]"
          @mouseover="highlightSlice(index)"
          @mouseout="unhighlightSlice()"
          @click="selectSlice(index)"
          @mousemove="updateTooltipPosition"
        />
      </g>
    </svg>
    <!-- Tooltip -->
    <div v-if="tooltipVisible" class="tooltip" :style="tooltipStyle">
      {{ hoveredLabel }}: {{ hoveredValue }}
    </div>
  </div>
</template>

<script setup>
import { computed, ref } from "vue";

const props = defineProps({
  data: {
    type: Array,
    required: true,
    // data是一个对象数组,每个对象有'value'和'label'键
  },
  size: {
    type: Number,
    default: 200,
  },
  colors: {
    type: Array,
    default: () => ["#ff6384", "#36a2eb", "#cc65fe", "#ffce56", "#4bc0c0"], // 默认颜色数组
  },
  normalRadius: {
    type: Number,
    default: 0.85, // 默认半径 (0 to 1)
  },
  hoverRadius: {
    type: Number,
    default: 0.9, // hover半径 (0 to 1)
  },
});

const totalValue = computed(() =>
  props.data.reduce((acc, cur) => acc + cur.value, 0)
);

const slices = computed(() => {
  let accumulatedAngle = 0;
  return props.data.map(({ value }, index, array) => {
    const startAngle = accumulatedAngle;
    const endAngle = accumulatedAngle + (value / totalValue.value) * 360;
    const sliceAngles = { startAngle, endAngle };
    if (index === array.length - 1 && Math.abs(endAngle - 360) > 1e-6) {
      sliceAngles.endAngle = 360;
    }
    accumulatedAngle = endAngle;
    return sliceAngles;
  });
});

const effectiveColors = computed(() => props.colors);

const highlightedIndex = ref(null);
const tooltipVisible = ref(false);
const hoveredLabel = ref("");
const hoveredValue = ref("");
const tooltipPosition = ref({ x: 0, y: 0 });

function generateArc(startAngle, endAngle, isHighlighted = false) {
  const r = isHighlighted ? props.hoverRadius : props.normalRadius;
  const startRadians = ((startAngle - 90) * Math.PI) / 180.0;
  const endRadians = ((endAngle - 90) * Math.PI) / 180.0;

  const start = polarToCartesian(r, startRadians);
  const end = polarToCartesian(r, endRadians);

  const largeArcFlag = Math.abs(endAngle - startAngle) <= 180 ? "0" : "1";
  return [
    `M ${start.x},${start.y}`,
    `A ${r},${r},0,${largeArcFlag},1,${end.x},${end.y}`,
    `L 0,0`,
    `Z`,
  ].join(" ");
}

function polarToCartesian(radius, angleInRadians) {
  return {
    x: Math.cos(angleInRadians) * radius,
    y: Math.sin(angleInRadians) * radius,
  };
}

function highlightSlice(index) {
  highlightedIndex.value = index;
  const { label, value } = props.data[index];
  hoveredLabel.value = label;
  hoveredValue.value = value;
  tooltipVisible.value = true;
}

function unhighlightSlice() {
  highlightedIndex.value = null;
  tooltipVisible.value = false;
}

function selectSlice(index) {
  console.log(`Selected slice: ${props.data[index].label}`);
}

function updateTooltipPosition(event) {
  if (!tooltipVisible.value) return;
  const svgRect = event.currentTarget.getBoundingClientRect();
  tooltipPosition.value = {
    x: event.clientX - svgRect.left - window.scrollX,
    y: event.clientY - svgRect.top - window.scrollY,
  };
}

const tooltipStyle = computed(() => ({
  left: `${tooltipPosition.value.x}px`,
  top: `${tooltipPosition.value.y}px`,
}));
</script>

<style scoped>
.pie-chart {
  display: inline-block;
  position: relative;
}

.tooltip {
  position: absolute;
  background-color: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 5px;
  border-radius: 3px;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

path {
  transition: fill 0.2s ease-out;
}
</style>
    <PieChart :data="chartData" :size="300" />

    import PieChart from './pieChart.vue';

    const chartData = [
      { label: 'Red', value: 20 },
      { label: 'Blue', value: 300 },
      { label: 'Purple', value: 100 },
      { label: 'Yellow', value: 400 }
    ];

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值