2023-12-18 06:11:34 +01:00
|
|
|
|
<template>
|
|
|
|
|
<svg viewBox="-3 -3 70 70">
|
|
|
|
|
<circle
|
|
|
|
|
v-for="(item, index) in sortedData"
|
2023-12-18 08:49:33 +01:00
|
|
|
|
cx="32"
|
|
|
|
|
cy="32"
|
|
|
|
|
r="16"
|
2023-12-18 06:11:34 +01:00
|
|
|
|
:class="[
|
|
|
|
|
$style.pie,
|
|
|
|
|
(focusedIndex === index) && $style.focused,
|
|
|
|
|
]"
|
|
|
|
|
:style="getStyle(index)"
|
|
|
|
|
:data-real-value="item"
|
|
|
|
|
/>
|
|
|
|
|
</svg>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
/**
|
|
|
|
|
* 軽量円グラフ描画コンポーネント(すごい)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<{
|
|
|
|
|
/** グラフにしたいデータ(数字の配列 or kv形式のオブジェクト) */
|
|
|
|
|
data: number[] | Record<string | number | symbol, number>;
|
|
|
|
|
/** 多い順にソートするか?(デフォルトON) */
|
|
|
|
|
sort?: boolean;
|
|
|
|
|
/** 小さな値を「その他」としてまとめるか?(デフォルトON、数値指定でその割合以下の項目をまとめる) */
|
|
|
|
|
truncMinor?: boolean | number;
|
|
|
|
|
/** グラデーション開始色([r, g, b]の配列で指定) */
|
|
|
|
|
startColor?: [number, number, number];
|
|
|
|
|
/** グラデーション終了色([r, g, b]の配列で指定) */
|
|
|
|
|
endColor?: [number, number, number];
|
|
|
|
|
/** 強調するindex番号を指定 */
|
|
|
|
|
focusedIndex?: number;
|
|
|
|
|
}>(), {
|
|
|
|
|
sort: true,
|
|
|
|
|
truncMinor: true,
|
|
|
|
|
startColor: [74, 179, 0],
|
|
|
|
|
endColor: [230, 255, 148],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isReady = ref(false);
|
|
|
|
|
|
|
|
|
|
const sortedData = computed(() => {
|
|
|
|
|
let out = Array.isArray(props.data) ? props.data : Object.values(props.data);
|
|
|
|
|
const sum = out.reduce((p, c) => p + c, 0);
|
|
|
|
|
|
|
|
|
|
if (props.sort) {
|
|
|
|
|
out = out.sort((a, b) => b - a);
|
|
|
|
|
}
|
|
|
|
|
if (props.truncMinor !== false) {
|
|
|
|
|
const ratio = props.truncMinor === true ? 0.02 : props.truncMinor;
|
|
|
|
|
const toBeTrunked = out.filter((v) => v / sum < ratio);
|
|
|
|
|
out = [
|
|
|
|
|
...out.filter((v) => v / sum >= ratio),
|
|
|
|
|
toBeTrunked.reduce((p, c) => p + c, 0),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const steppedColors = computed(() => {
|
|
|
|
|
const r = sortedData.value.map((_, i, a) => props.startColor[0] - (props.startColor[0] - props.endColor[0]) / a.length * i);
|
|
|
|
|
const g = sortedData.value.map((_, i, a) => props.startColor[1] - (props.startColor[1] - props.endColor[1]) / a.length * i);
|
|
|
|
|
const b = sortedData.value.map((_, i, a) => props.startColor[2] - (props.startColor[2] - props.endColor[2]) / a.length * i);
|
|
|
|
|
return sortedData.value.map((_, i) => [r[i], g[i], b[i]]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const dasharrays = computed(() => {
|
|
|
|
|
const sum = sortedData.value.reduce((p, c) => p + c, 0);
|
|
|
|
|
|
|
|
|
|
const out: number[][] = [];
|
|
|
|
|
let start = 0;
|
|
|
|
|
for (let index = 0; index < sortedData.value.length; index++) {
|
|
|
|
|
const element = sortedData.value[index];
|
|
|
|
|
const percentage = element / sum;
|
|
|
|
|
if (index === 0) {
|
|
|
|
|
out[index] = [percentage * 100, 100 - percentage * 100];
|
|
|
|
|
start = percentage * 100;
|
|
|
|
|
} else {
|
|
|
|
|
out[index] = [
|
|
|
|
|
0,
|
|
|
|
|
start,
|
|
|
|
|
percentage * 100,
|
|
|
|
|
Math.max(100 - (start + (percentage * 100)), 0),
|
|
|
|
|
];
|
|
|
|
|
start += percentage * 100;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out.map((v) => v.join(' '));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function getStyle(index: number) {
|
|
|
|
|
return `stroke: rgb(${steppedColors.value[index][0]}, ${steppedColors.value[index][1]}, ${steppedColors.value[index][2]});
|
2023-12-18 08:49:33 +01:00
|
|
|
|
stroke-dasharray: ${dasharrays.value[index]}`;
|
2023-12-18 06:11:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
watch(dasharrays, (to) => {
|
|
|
|
|
if (to.length > 0) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
isReady.value = true;
|
|
|
|
|
}, 100);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style module>
|
|
|
|
|
.pie {
|
|
|
|
|
@apply transition-transform;
|
|
|
|
|
transform-origin: center;
|
|
|
|
|
fill: transparent;
|
|
|
|
|
stroke-width: 32;
|
|
|
|
|
stroke-dashoffset: 25;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.focused.pie {
|
|
|
|
|
transform: scale(1.08);
|
|
|
|
|
}
|
|
|
|
|
</style>
|