饼图组件(3D)优化折线展示效果

This commit is contained in:
郭永超 2025-07-23 17:19:33 +08:00
parent df80a4bb84
commit 2b1875c2da
4 changed files with 239 additions and 48 deletions

View File

@ -52,6 +52,11 @@ export default {
type: Boolean,
default: false,
},
// 线grid3D.viewControl.beta线
isShowLine: {
type: Boolean,
default: false,
},
},
emits: ['click'],
setup(props, { emit }) {
@ -180,6 +185,19 @@ export default {
startValue = endValue;
legendData.push(series[i].name);
}
// 线lable
if (props.isShowLine) {
series.forEach((item, index) => {
let {
itemStyle: { color },
pieData: { startRatio, endRatio, value },
} = item;
addLabelLine(series, startRatio, endRatio, value, k, index, color);
});
}
const option = Object.assign(
{
tooltip: {
@ -289,6 +307,100 @@ export default {
function onClick(params) {
emit('click', params);
}
//label线
/**
* @param {*} series 配置
* @param {*} startRatio 扇形起始位置比例
* @param {*} endRatio 扇形结束位置比例
* @param {*} value 数值
* @param {*} k 辅助参数饼图半径相关
* @param {*} i 在series中索引
* @param {*} color 指示线颜色
*/
function addLabelLine(series, startRatio, endRatio, value, k, i, color = '#fff') {
//
const midRadian = (startRatio + endRatio) * Math.PI;
const radius = 1 + k; //
// 1.
const startPos = [Math.cos(midRadian) * radius, Math.sin(midRadian) * radius, 0.1 * value];
// 2.
const isRightHalf = Math.cos(midRadian) > 0;
const isTopHalf = Math.sin(midRadian) > 0;
// 3. 线
const baseLength = 0.5 + 0.05 * (1 - series.length / 10); //
const lineSegment1 = baseLength * 0.8; //
const lineSegment2 = baseLength * 1.2; //
// 4. 线
//
const turningPos = [startPos[0] + Math.cos(midRadian) * lineSegment1, startPos[1] + Math.sin(midRadian) * lineSegment1, startPos[2]];
//
const endPos = [
turningPos[0] + (isRightHalf ? lineSegment2 : -lineSegment2),
turningPos[1] + (isTopHalf ? -0.1 : 0.1) * (1 + i * 0.03), //
turningPos[2],
];
// 5.
const textPos = [
endPos[0] * (isRightHalf ? 1.1 : 0.9), //
endPos[1] + (isTopHalf ? -0.05 : 0.05),
endPos[2],
];
// series线+
series.push(
{
type: 'line3D',
lineStyle: {
color: '#ffffff', // 线
width: 1.8,
opacity: 0.8,
},
data: [startPos, turningPos],
zlevel: 10,
},
{
type: 'line3D',
lineStyle: {
color: '#ffffff', // 线
width: 1.8,
opacity: 0.6,
},
data: [turningPos, endPos],
zlevel: 10,
},
{
type: 'scatter3D',
label: {
show: true,
position: isRightHalf ? 'right' : 'left',
distance: 5,
textStyle: {
color: color, //
fontSize: 14,
// fontWeight: 'bold',
backgroundColor: 'transparent', //
},
formatter: `{b}\n${value} ${props.option.legendSuffix ?? ''}`, //
},
symbolSize: 0,
data: [
{
name: series[i].name,
value: textPos, // 使
},
],
zlevel: 11,
}
);
}
return { chartRef };
},
};

View File

@ -0,0 +1,108 @@
<template>
<div class="inputs">
<h2 class="inputs-title">全县投入品数量291.85万吨</h2>
<new-hyaline-cake :chart-data="state.data" :option="state.option" :width="'100%'" :height="'188px'" />
</div>
</template>
<script setup>
import { reactive, watch } from 'vue';
const state = reactive({
option: {
k: 0,
opacity: 0.8,
itemGap: 0.1,
// legendSuffix: '',
itemHeight: 200,
startAngle: 60,
grid3D: {
show: false,
boxHeight: 2,
top: '-20',
bottom: '10',
// left: '-20%',
viewControl: {
//3d
alpha: 40, //( )
beta: -40,
distance: 260, //zoom()
rotateSensitivity: 0, //0
zoomSensitivity: 0, //0
panSensitivity: 0, //0
autoRotate: false, //
autoRotateAfterStill: 2, //, autoRotate
},
},
legend: {
show: true,
top: 'bottom',
orient: 'horizontal',
itemWidth: 12, //
itemHeight: 12, //
itemGap: 20,
icon: 'rect',
left: 'center',
textStyle: {
color: '#fff',
fontSize: 16,
},
formatter: (name) => name,
},
pie2dConfig: {
center: ['50%', '40%'],
label: {
show: true,
position: 'outside',
formatter: (params) => {
const total = 221.8 + 70.01; //
const percent = ((params.value / total) * 100).toFixed(0);
return `{name|${params.name}}\n{value|${params.value} 万吨\n${percent}%}`;
},
rich: {
name: {
fontSize: 16,
fontWeight: 'bold',
color: '#ffffff',
lineHeight: 20,
align: 'center',
},
value: {
fontSize: 16,
color: '#79F5AF',
lineHeight: 18,
align: 'center',
},
},
color: '#fff',
},
labelLine: {
show: true,
length: 40,
length2: 40,
lineStyle: { color: '#fff', width: 2 },
},
},
},
data: [
{ value: 221.8, name: '合资公司' },
{ value: 70.01, name: '其它实体', floatZ: 1 },
],
});
</script>
<style lang="scss" scoped>
.inputs {
&-title {
width: 270px;
height: 40px;
line-height: 40px;
margin: 24px auto 0;
background-image: url('@/assets/images/inputs/bg_title.png');
background-position: center bottom;
background-repeat: no-repeat;
background-size: 100%;
font-size: 20px;
font-weight: 400;
color: #fff;
text-align: center;
}
}
</style>

View File

@ -1,30 +1,33 @@
<template>
<div class="inputs">
<h2 class="inputs-title">全县投入品数量291.85万吨</h2>
<new-hyaline-cake :chart-data="state.data" :option="state.option" :width="'100%'" :height="'188px'" />
<h2 class="inputs-title">全县投入品数量291.81万吨</h2>
<!-- <div style="height: 50px"></div> -->
<custom-echart-hyaline-cake :chart-data="state.data" :option="state.option" :width="'100%'" :height="'188px'" :is-show-line="true" />
</div>
</template>
<script setup>
import { reactive, watch } from 'vue';
import { ref, reactive, watch, computed } from 'vue';
const state = reactive({
option: {
k: 0,
k: 1,
opacity: 0.8,
itemGap: 0.1,
// legendSuffix: '',
legendSuffix: '万吨',
itemHeight: 200,
startAngle: 60,
grid3D: {
show: false,
show: true,
boxHeight: 2,
top: '-20',
bottom: '10',
top: '-10%',
bottom: '0',
// left: '-20%',
viewControl: {
//3d
alpha: 40, //( )
beta: -40,
distance: 260, //zoom()
//3d线
//2 -203 -104 05 10
beta: -20,
distance: 300, //zoom()
rotateSensitivity: 0, //0
zoomSensitivity: 0, //0
panSensitivity: 0, //0
@ -34,7 +37,7 @@ const state = reactive({
},
legend: {
show: true,
top: 'bottom',
top: '90%',
orient: 'horizontal',
itemWidth: 12, //
itemHeight: 12, //
@ -47,44 +50,13 @@ const state = reactive({
},
formatter: (name) => name,
},
pie2dConfig: {
center: ['50%', '40%'],
label: {
show: true,
position: 'outside',
formatter: (params) => {
const total = 221.8 + 70.01; //
const percent = ((params.value / total) * 100).toFixed(0);
return `{name|${params.name}}\n{value|${params.value} 万吨\n${percent}%}`;
},
rich: {
name: {
fontSize: 16,
fontWeight: 'bold',
color: '#ffffff',
lineHeight: 20,
align: 'center',
},
value: {
fontSize: 16,
color: '#79F5AF',
lineHeight: 18,
align: 'center',
},
},
color: '#fff',
},
labelLine: {
show: true,
length: 40,
length2: 40,
lineStyle: { color: '#fff', width: 2 },
},
},
},
data: [
{ value: 221.8, name: '合资公司' },
{ value: 70.01, name: '其它实体', floatZ: 1 },
{ value: 70.01, name: '其它实体' },
// { value: 55.03, name: '' },
// { value: 33.06, name: '' },
// { value: 47.6, name: '' },
],
});
</script>
@ -94,7 +66,7 @@ const state = reactive({
width: 270px;
height: 40px;
line-height: 40px;
margin: 24px auto 0;
margin: 10px auto 0;
background-image: url('@/assets/images/inputs/bg_title.png');
background-position: center bottom;
background-repeat: no-repeat;

View File

@ -12,7 +12,6 @@ declare module 'vue' {
BreadComp: typeof import('./src/components/breadComp.vue')['default']
CenterMap: typeof import('./src/components/centerMap.vue')['default']
CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default']
copy: typeof import('./src/components/custom-scroll-title copy/index.vue')['default']
CostomImg: typeof import('./src/components/costomImg.vue')['default']
CustomBack: typeof import('./src/components/customBack.vue')['default']
CustomCarouselPicture: typeof import('./src/components/custom-carousel-picture/index.vue')['default']