feat(Charts): 新增3D柱状图和3D环状饼图组件

新增3D柱状图组件,包含数据配置、样式调整和交互功能
新增3D环状饼图组件,支持多种标签样式、图例配置和3D效果调整
为两种图表添加完整的配置项和交互功能
This commit is contained in:
gaohaifeng 2025-08-25 15:16:38 +08:00
parent 9c2fa974ac
commit 1bf9ffb78b
12 changed files with 1449 additions and 2 deletions

View File

@ -0,0 +1,79 @@
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
import { Bar3DConfig } from './index'
import { CreateComponentType } from '@/packages/index.d'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export const includes = ['legend', 'xAxis', 'yAxis', 'grid', 'dataZoom']
export const seriesItem = {
type: 'bar',
barWidth: 19,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 1,
x2: 0,
y2: 0,
colorStops: [
{ offset: 0, color: '#01c5ff' }, // 底部颜色(深蓝)
{ offset: 1, color: '#fbff01ff' } // 顶部颜色(浅蓝)
]
},
borderRadius: 2
},
}
export const option = {
tooltip: {
show: false,
trigger: 'axis',
axisPointer: {
show: false,
type: 'shadow'
}
},
legend: {
show: false // 新增或修改这行,隐藏图例(即“当日作业情况”文字和旁边的小点)
},
xAxis: {
show: false,
type: 'category'
},
yAxis: {
show: false,
type: 'value',
min: 0,
max: 100,
interval: 25
},
dataZoom: [
{
show: false,
height: 15,
start: 0,
end: 100
},
{
show: false,
yAxisIndex: 0,
filterMode: 'empty',
width: 15,
height: 200,
showDataShadow: false,
left: '93%',
start: 0,
end: 100
},
],
dataset: { ...dataJson },
series: [seriesItem]
}
export default class Config extends PublicConfigClass implements CreateComponentType {
public key = Bar3DConfig.key
public chartConfig = cloneDeep(Bar3DConfig)
public option = echartOptionProfixHandle(option, includes)
}

View File

@ -0,0 +1,82 @@
<template>
<!-- Echarts 全局设置 -->
<global-setting :optionData="optionData"></global-setting>
<!-- <CollapseItem name="额外刻度设置" :expanded="true">
<SettingItemBox name="Y轴最小值">
<n-input-number v-model:value="yAxisData?.min" :min="0" size="small"></n-input-number>
</SettingItemBox>
<SettingItemBox name="Y轴最大值">
<n-input-number v-model:value="yAxisData?.max" :min="0" size="small"></n-input-number>
</SettingItemBox>
<SettingItemBox name="Y轴步长">
<n-input-number v-model:value="yAxisData?.interval" :min="1" size="small"></n-input-number>
</SettingItemBox>
</CollapseItem> -->
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index + 1}`" :expanded="true">
<SettingItemBox name="图形">
<SettingItem name="宽度">
<n-input-number
v-model:value="item.barWidth"
:min="1"
:max="100"
size="small"
placeholder="自动计算"
></n-input-number>
</SettingItem>
<SettingItem name="圆角">
<n-input-number v-model:value="item.itemStyle.borderRadius" :min="0" size="small"></n-input-number>
</SettingItem>
<SettingItem name="底部颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color.colorStops[0].color"></n-color-picker>
</SettingItem>
<SettingItem name="顶部颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color.colorStops[1].color"></n-color-picker>
</SettingItem>
</SettingItemBox>
<setting-item-box name="标签">
<setting-item>
<n-space>
<n-switch v-model:value="item.label.show" size="small" />
<n-text>展示标签</n-text>
</n-space>
</setting-item>
<setting-item name="大小">
<n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
</setting-item>
<setting-item name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
</setting-item>
<setting-item name="位置">
<n-select
v-model:value="item.label.position"
:options="[
{ label: 'top', value: 'top' },
{ label: 'left', value: 'left' },
{ label: 'right', value: 'right' },
{ label: 'bottom', value: 'bottom' }
]"
/>
</setting-item>
</setting-item-box>
</CollapseItem>
</template>
<script setup lang="ts">
import { BaseTransitionPropsValidators, PropType, computed } from 'vue'
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
const props = defineProps({
optionData: {
type: Object as PropType<GlobalThemeJsonType>,
required: true
}
})
const seriesList = computed(() => {
return props.optionData.series
})
const yAxisData = computed(() => {
return props.optionData.yAxis
})
</script>

View File

@ -0,0 +1,9 @@
{
"dimensions": ["场所分布情况", "当日作业分布情况"],
"source": [
{
"场所分布情况": 150,
"当日作业分布情况": 75
}
]
}

View File

@ -0,0 +1,14 @@
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
export const Bar3DConfig: ConfigType = {
key: 'Bar3D',
chartKey: 'VBar3D',
conKey: 'VCBar3D',
title: '3D柱状图',
category: ChatCategoryEnum.BAR,
categoryName: ChatCategoryEnumName.BAR,
package: PackagesCategoryEnum.CHARTS,
chartFrame: ChartFrameEnum.ECHARTS,
image: 'bar_x.png'
}

View File

@ -0,0 +1,97 @@
<template>
<v-chart
ref="vChartRef"
:init-options="initOptions"
:theme="themeColor"
:option="option"
:update-options="{
replaceMerge: replaceMergeArr
}"
autoresize
></v-chart>
</template>
<script setup lang="ts">
import { ref, nextTick, computed, watch, PropType } from 'vue'
import VChart from 'vue-echarts'
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { BarChart } from 'echarts/charts'
import config, { includes, seriesItem } from './config'
import { mergeTheme } from '@/packages/public/chart'
import { useChartDataFetch } from '@/hooks'
import { CreateComponentType } from '@/packages/index.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { isPreview } from '@/utils'
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'
import isObject from 'lodash/isObject'
import cloneDeep from 'lodash/cloneDeep'
const props = defineProps({
themeSetting: {
type: Object,
required: true
},
themeColor: {
type: Object,
required: true
},
chartConfig: {
type: Object as PropType<config>,
required: true
}
})
const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting)
use([DatasetComponent, CanvasRenderer, BarChart, GridComponent, TooltipComponent, LegendComponent])
const replaceMergeArr = ref<string[]>()
const option = computed(() => {
return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
})
// dataset
watch(
() => props.chartConfig.option.dataset,
(newData: { dimensions: any }, oldData) => {
try {
if (!isObject(newData) || !('dimensions' in newData)) return
if (Array.isArray(newData?.dimensions)) {
const seriesArr = []
// oldData
// dimensionsYdimensions.length011X
const oldDimensions = Array.isArray(oldData?.dimensions)&&oldData.dimensions.length >= 1 ? oldData.dimensions.length : 1;
const newDimensions = newData.dimensions.length >= 1 ? newData.dimensions.length : 1;
const dimensionsGap = newDimensions - oldDimensions;
if (dimensionsGap < 0) {
props.chartConfig.option.series.splice(newDimensions - 1)
} else if (dimensionsGap > 0) {
if(!oldData || !oldData?.dimensions || !Array.isArray(oldData?.dimensions) || !oldData?.dimensions.length ) {
props.chartConfig.option.series=[]
}
for (let i = 0; i < dimensionsGap; i++) {
seriesArr.push(cloneDeep(seriesItem))
}
props.chartConfig.option.series.push(...seriesArr)
}
replaceMergeArr.value = ['series']
nextTick(() => {
replaceMergeArr.value = []
})
}
} catch (error) {
console.log(error)
}
},
{
deep: false
}
)
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
props.chartConfig.option.dataset = newData
})
</script>

View File

@ -7,6 +7,7 @@ import { BarRankConfig } from './BarRank'
import { BarRank2Config } from './BarRank2'
import { BarRank3Config } from './BarRank3'
import { BarLinearConfig } from './BarLinear/index'
import { Bar3DConfig } from './Bar3d/index'
export default [
BarCommonConfig,
@ -17,5 +18,6 @@ export default [
BarRankConfig,
BarRank2Config,
BarRank3Config,
BarLinearConfig
BarLinearConfig,
Bar3DConfig
]

View File

@ -0,0 +1,100 @@
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { PieConfig } from './index'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export const includes = ['legend']
export const indicatorPlacements = [
{
label: '内部',
value: 'inside'
},
{
label: '外部',
value: 'outside'
}
]
export enum FontWeightEnum {
NORMAL = '常规',
BOLD = '加粗',
}
export const FontWeightObject = {
[FontWeightEnum.NORMAL]: 'normal',
[FontWeightEnum.BOLD]: 'bold',
}
export enum LegendOrientEnum {
HORIZONTAL = '水平',
VERTICAL = '垂直'
}
export const legendOrientObject = {
[LegendOrientEnum.HORIZONTAL]: 'horizontal',
[LegendOrientEnum.VERTICAL]: 'vertical'
}
const option = {
renderer: 'canvas',
dataset: dataJson,
labelStyleType: '样式一',
labelColor1: '#ffffff',
labelColor2: '#ffffff',
labelPadding1: "0,3,0,-5",
labelPadding2: "0,3,-10,5",
showDataLabels: false,
showLegendValue: false,
showLegendPercent: false,
showLableLineImage: false,
labelLineColor: '#ffffff',
lableLineLength1: 30,
lableLineLength2: 40,
series: [],
legend: {
itemGap: 10,
x: 0,
y: 0,
orient: 'vertical',
// top: "center",
//@ts-ignore
data: dataJson,
icon: 'rectangle',
textStyle: {
color: '#fff',
fontSize: 14,
padding: [5, 0],
}
},
grid3D: {
show: false,
boxHeight: 3, //圆环的高度
//@ts-ignore
left: '-15%',
//@ts-ignore
top: '5%', //3d饼图的位置
viewControl: {
//3d效果可以放大、旋转等请自己去查看官方配置
alpha: 15, //角度
distance: 280, //调整视角到主体的距离类似调整zoom
rotateSensitivity: 0, //设置为0无法旋转
zoomSensitivity: 1, //设置为0无法缩放
panSensitivity: 1, //设置为0无法平移
autoRotate: false, //自动旋转
},
},
}
export default class Config extends PublicConfigClass implements CreateComponentType {
public key: string = PieConfig.key
public chartConfig = cloneDeep(PieConfig)
// 图表配置项
public option = echartOptionProfixHandle(option, includes)
}

View File

@ -0,0 +1,122 @@
<template>
<!-- 默认展开 -->
<global-setting :optionData="config"></global-setting>
<CollapseItem name="柱状图3D" :expanded="true">
<SettingItemBox name="三维配置">
<SettingItem name="视距">
<n-input-number v-model:value="config.grid3D!.viewControl.distance" size="small" :min="0" placeholder="视距"></n-input-number>
</SettingItem>
<SettingItem name="Y轴">
<n-input-number v-model:value="config.grid3D!.viewControl.alpha" size="small" :min="0" placeholder="y轴"></n-input-number>
</SettingItem>
<SettingItem name="Z轴">
<n-input-number v-model:value="config.grid3D!.boxHeight" size="small" :min="0" placeholder="Z轴"></n-input-number>
</SettingItem>
<!-- <SettingItem name="饼图大小">
<n-input-number v-model:value="optionData.size" size="small" :min="0" placeholder="饼图大小"></n-input-number>
</SettingItem> -->
<!-- <SettingItem name="透明度">
<n-input-number v-model:value="optionData.opacity" size="small" :min="0" :max="1" placeholder="透明度"></n-input-number>
</SettingItem> -->
<setting-item>
<n-space>
<n-switch v-model:value="config.showDataLabels" size="small" />
<n-text>显示指标线</n-text>
</n-space>
</setting-item>
<setting-item name="指标线颜色" v-if="config.showDataLabels">
<n-color-picker size="small" :modes="['hex']" v-model:value="config.labelLineColor"></n-color-picker>
</setting-item>
<SettingItem name="指标线长度1" v-if="config.showDataLabels">
<n-input-number v-model:value="config.lableLineLength1" size="small" :min="0" placeholder="指标线长度1"></n-input-number>
</SettingItem>
<SettingItem name="指标线长度2" v-if="config.showDataLabels">
<n-input-number v-model:value="config.lableLineLength2" size="small" :min="0" placeholder="指标线长度2"></n-input-number>
</SettingItem>
<setting-item name="指标样式类型">
<n-select v-model:value="config.labelStyleType" size="small" :options="labelTypeOptions" />
</setting-item>
<setting-item name="指标文字颜色1" v-if="config.showDataLabels">
<n-color-picker size="small" :modes="['hex']" v-model:value="config.labelColor1"></n-color-picker>
</setting-item>
<setting-item name="指标文字颜色2" v-if="config.showDataLabels">
<n-color-picker size="small" :modes="['hex']" v-model:value="config.labelColor2"></n-color-picker>
</setting-item>
<setting-item name="指标文字位置1" v-if="config.showDataLabels">
<n-input v-model:value="config.labelPadding1" type="text" size="small"></n-input>
</setting-item>
<setting-item name="指标文字位置2" v-if="config.showDataLabels">
<n-input v-model:value="config.labelPadding2" type="text" size="small"></n-input>
</setting-item>
<setting-item v-if="config.showDataLabels">
<n-space>
<n-switch v-model:value="config.showLableLineImage" size="small" />
<n-text>指标线显示图片</n-text>
</n-space>
</setting-item>
<setting-item>
<n-space>
<n-switch v-model:value="config.showLegendValue" size="small" />
<n-text>图例显示数值</n-text>
</n-space>
</setting-item>
<setting-item>
<n-space>
<n-switch v-model:value="config.showLegendPercent" size="small" />
<n-text>图例显示百分比</n-text>
</n-space>
</setting-item>
</SettingItemBox>
</CollapseItem>
</template>
<script setup lang="ts">
import { computed, PropType } from 'vue'
//
// option 便使 typeof
import { FontWeightEnum, FontWeightObject, LegendOrientEnum, legendOrientObject } from './config'
//
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { GlobalThemeJsonType } from '@/settings/chartThemes'
const props = defineProps({
optionData: {
type: Object as PropType<GlobalThemeJsonType>,
required: true
}
})
const fontWeightOptions = [
{
label: FontWeightEnum.NORMAL,
value: FontWeightObject[FontWeightEnum.NORMAL]
},
{
label: FontWeightEnum.BOLD,
value: FontWeightObject[FontWeightEnum.BOLD]
}
]
const legendOrients = [
{
label: LegendOrientEnum.HORIZONTAL,
value: legendOrientObject[LegendOrientEnum.HORIZONTAL]
},
{
label: LegendOrientEnum.VERTICAL,
value: legendOrientObject[LegendOrientEnum.VERTICAL]
}
]
const labelTypeOptions = [
{ label: '样式一', value: '样式一' },
{ label: '样式二', value: '样式二' },
{ label: '样式三', value: '样式三' },
]
const config = computed(() => {
return props.optionData
})
</script>

View File

@ -0,0 +1,27 @@
[
{
"name": "分类一",
"value": 94,
"itemStyle": { "color": "#9751c2" },
"ratio": 0,
"isSelected": true
},
{
"name": "分类二",
"value": 82,
"itemStyle": { "color": "#199ed4" },
"ratio": 0
},
{
"name": "分类三",
"value": 78,
"itemStyle": { "color": "#06c1cd" },
"ratio": 0
},
{
"name": "分类四",
"value": 60,
"itemStyle": { "color": "#f7a35c" },
"ratio": 0
}
]

View File

@ -0,0 +1,23 @@
// 公共类型声明
import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d'
// 当前[信息模块]分类声明
import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
export const PieConfig: ConfigType = {
// 唯一key
key: 'Pie',
// 图表组件渲染 Components 格式: V + key
chartKey: 'VPie',
// 配置组件渲染 Components 格式: VC + key
conKey: 'VCPie',
// 名称
title: '3D环状饼图',
// 子分类目录
category: ChatCategoryEnum.PIE,
// 子分类目录
categoryName: ChatCategoryEnumName.PIE,
// 包分类
package: PackagesCategoryEnum.CHARTS,
// 图片
image: 'pie_3d.png'
}

View File

@ -0,0 +1,891 @@
<template>
<v-chart
ref="vChartRef"
autoresize
:init-options="initOptions"
:theme="themeColor"
:update-options="{ notMerge: true }"
:option="option.value"
></v-chart>
</template>
<script setup lang="ts">
import 'echarts-gl'
import { toRaw, toReadonly, toRefs } from '@vue/reactivity'
import { isPreview } from '@/utils'
import { computed, onMounted, PropType, reactive, watch } from 'vue'
import VChart from 'vue-echarts'
import * as echarts from 'echarts'
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { PieChart } from 'echarts/charts'
import { mergeTheme } from '@/packages/public/chart'
import config, { includes } from './config'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent, TitleComponent } from 'echarts/components'
const props = defineProps({
themeSetting: {
type: Object,
required: true
},
themeColor: {
type: Object,
required: true
},
chartConfig: {
type: Object as PropType<config>,
required: true
}
})
const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting)
const chartEditStore = useChartEditStore()
use([DatasetComponent, CanvasRenderer, PieChart, GridComponent, TooltipComponent, LegendComponent, TitleComponent])
const {
legend,
grid3D,
showDataLabels,
showLegendValue,
showLegendPercent,
showLableLineImage,
labelLineColor,
labelColor1,
labelColor2,
labelPadding1,
labelPadding2,
lableLineLength1,
lableLineLength2,
labelStyleType
} = toRefs(props.chartConfig.option)
// const { dataset, alpha, beta, depth, size, showDataLabels, innerSize, opacity } = toRefs(props.chartConfig.option)
const option = reactive({
value: {}
})
//
const fontSize = (res: any) => {
const clientWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
if (!clientWidth) return
const fontSize = 40 * (clientWidth / 1920)
console.log(fontSize)
return res * fontSize
}
const dataHandle = (newData: any) => {
if (newData) {
//@ts-ignore
getPie3D(newData, newData[0].ratio)
//label线2d使labelLine3dsetOption
props.chartConfig.option.series.push({
name: 'pie2d',
type: 'pie',
labelLine: {
show: showDataLabels?.value || false,
length: lableLineLength1?.value || 30,
length2: lableLineLength2?.value || 30,
lineStyle: {
color: labelLineColor?.value || '#fff',
width: 2
}
},
label: {
opacity: showDataLabels.value ? 1 : 0,
show: true,
formatter: showLableLineImage?.value
? labelStyleType?.value === '样式一'
? ' {c|{c}个}\n {img|}\n {a| 占比{d}%}'
: labelStyleType?.value === '样式二'
? ' {style|{c} 个}\n {img|}\n {e|{b}}'
: ' {style|{d}%}\n {img|}\n {f|{b}}'
: labelStyleType?.value === '样式一'
? ' {c|{c}个}\n {dot|}\n {a| 占比{d}%}'
: labelStyleType?.value === '样式二'
? ' {style|{c} 个}\n {dot|}\n{e|{b}}'
: ' {style|{d}%}\n {dot|}\n {f|{b}}',
rich: {
dot: {
backgroundColor: '#fff', //
width: 0,
height: 0,
borderRadius: 5, //
fontSize: 16,
padding: [5, -5, 5, -5] // 6
},
img: {
backgroundColor: { image: '/src/assets/images/chart/charts/dot.png' }, //
width: 24,
height: 24,
// borderRadius: 5, //
// fontSize: 16,
padding: [0, -30, 0, -15]
},
a: {
fontSize: 14,
color: labelColor2?.value || '#fff',
align: 'left',
padding: labelPadding2?.value.split(',').map(function (str: any) {
return parseInt(str)
}) || [0, 3, 0, -5]
},
c: {
fontSize: 20,
color: labelColor1?.value || '#41a6fc',
align: 'left',
padding: labelPadding1?.value.split(',').map(function (str: any) {
return parseInt(str)
}) || [0, 3, -10, 5]
},
style: {
fontSize: 20,
color: labelColor1?.value || '#b8c276',
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
// { offset: 0, color: '#fefefe' }, //
// { offset: 1, color: '#e0e98d' } //
// ]),
align: 'left',
padding: labelPadding1?.value.split(',').map(function (str: any) {
return parseInt(str)
}) || [0, 3, -10, 5]
},
e: {
fontSize: 14,
color: labelColor2?.value || '#fff',
align: 'left',
padding: labelPadding2?.value.split(',').map(function (str: any) {
return parseInt(str)
}) || [0, 0, 0, 23]
},
f: {
fontSize: 14,
color: labelColor2?.value || '#fff',
align: 'left',
padding: labelPadding2?.value.split(',').map(function (str: any) {
return parseInt(str)
}) || [0, 0, 0, 3]
}
}
},
silent: true,
startAngle: 320, //[0, 360]
clockwise: false, //3d
//@ts-ignore
radius: [fontSize(0.6) + '%', fontSize(0.8) + '%'],
//@ts-ignore
center: [fontSize(1) + '%', fontSize(1.4) + '%'], //线
data: newData,
itemStyle: {
opacity: 0
}
})
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
option.value = props.chartConfig.option
}
}
const getPie3D = (pieData: any, internalDiameterRatio: any) => {
//internalDiameterRatio:
//@ts-ignore
let series: any[] = []
let sumValue = 0
let startValue = 0
let endValue = 0
let k = 1 - internalDiameterRatio
pieData.sort((a: any, b: any) => {
return b.value - a.value
})
// series-surface
for (let i = 0; i < pieData.length; i++) {
sumValue += pieData[i].value
let seriesItem = {
name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
type: 'surface',
parametric: true,
wireframe: {
show: false
},
pieData: pieData[i],
itemStyle: pieData[i].itemStyle,
pieStatus: {
selected: false,
hovered: false,
k: k
},
radius: '20%',
center: ['0%', '20%']
}
series.push(seriesItem)
}
// 使 sumValue getParametricEquation
// series-surface series-surface.parametricEquation
for (let i = 0; i < series.length; i++) {
endValue = startValue + series[i].pieData.value
series[i].pieData.startRatio = startValue / sumValue
series[i].pieData.endRatio = endValue / sumValue
//@ts-ignore
series[i].parametricEquation = getParametricEquation(
series[i].pieData.startRatio,
series[i].pieData.endRatio,
series[i].pieData.isSelected ?? false,
false,
k,
series[i].pieData.value
)
startValue = endValue
}
// let boxHeight = getHeight3D(series, 15); //3d/2626px
// legendDataseries
// @ts-nocheck
//@ts-ignore
props.chartConfig.option = {
renderer: 'canvas',
// labelLine: {
// show: true,
// length: 15,
// length2: 15,
// },
dataset: pieData,
showLableLineImage: showLableLineImage?.value || false,
labelColor1: labelColor1?.value || '',
labelColor2: labelColor2?.value || '',
labelPadding1: labelPadding1?.value || '',
labelPadding2: labelPadding2?.value || '',
labelStyleType: labelStyleType?.value || '样式一',
labelLineColor: labelLineColor?.value || '#fff',
lableLineLength1: lableLineLength1?.value || 30,
lableLineLength2: lableLineLength2?.value || 30,
showLegendValue: showLegendValue?.value || false,
showLegendPercent: showLegendPercent?.value || false,
showDataLabels: showDataLabels?.value || false,
legend: {
show: legend.value.show,
itemGap: legend.value.itemGap,
x: legend.value.x,
y: legend.value.y,
orient: legend.value.orient,
// top: "center",
//@ts-ignore
data: pieData,
icon: 'rectangle',
//@ts-ignore
itemWidth: fontSize(0.3), //
//@ts-ignore
itemHeight: fontSize(0.25), //
//@ts-ignore
// formatter: res => {
// let str = ''
// series.forEach(ele => {
// if (res == ele.name) {
// let bfb = ((ele.pieData.endRatio - ele.pieData.startRatio) * 100).toFixed(2)
// str = ele.pieData.name
// if (showLegendValue.value) {
// str += " " + ele.pieData.value
// }
// }
// })
// return str
// },
textStyle: {
color: '#fff',
fontSize: legend.value.textStyle.fontSize,
padding: [5, 0]
}
},
//@ts-ignore
tooltip: {
show: true,
//@ts-ignore
formatter: params => {
if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
//@ts-ignore
let bfb = (
(series[params.seriesIndex].pieData.endRatio -
//@ts-ignore
series[params.seriesIndex].pieData.startRatio) *
100
).toFixed(2)
return (
`${params.seriesName}<br/>` +
`<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>` +
`${bfb}%`
)
}
}
},
xAxis3D: {
min: -1,
max: 1
},
yAxis3D: {
min: -1,
max: 1
},
zAxis3D: {
min: -1,
max: 1
},
grid3D: {
show: false,
boxHeight: grid3D.value.boxHeight, //
//@ts-ignore
left: '-15%',
//@ts-ignore
top: '5%', //3d
bottom: '5%',
viewControl: {
//3d
alpha: grid3D.value.viewControl.alpha, //
distance: grid3D.value.viewControl.distance, //zoom
rotateSensitivity: 0, //0
zoomSensitivity: 1, //0
panSensitivity: 1, //0
autoRotate: false //
}
// postEffect: {
// //齿
// enable: true,
// bloom: {
// enable: true,
// bloomIntensity: 0.1,
// },
// SSAO: {
// enable: true,
// quality: "medium",
// radius: 2,
// },
// },
},
series: series
}
}
const getParametricEquation = (startRatio: any, endRatio: any, isSelected: any, isHovered: any, k: any, h: any) => {
//
let midRatio = (startRatio + endRatio) / 2
let startRadian = startRatio * Math.PI * 2
let endRadian = endRatio * Math.PI * 2
let midRadian = midRatio * Math.PI * 2
//
if (startRatio === 0 && endRatio === 1) {
isSelected = false
}
// / k 1/3
k = typeof k !== 'undefined' ? k : 1 / 3
// x y 0
let offsetX = isSelected ? Math.cos(midRadian) * 0.2 : 0
let offsetY = isSelected ? Math.sin(midRadian) * 0.2 : 0
let offsetZ = isSelected ? Math.sin(midRadian) * 0.5 : 0
// 1
let hoverRate = isHovered ? 1.1 : 1
//
return {
u: {
min: -Math.PI,
max: Math.PI * 3,
step: Math.PI / 32
},
v: {
min: 0,
max: Math.PI * 2,
step: Math.PI / 20
},
x: function (u: any, v: any) {
if (u < startRadian) {
return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
}
if (u > endRadian) {
return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
}
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate
},
y: function (u: any, v: any) {
if (u < startRadian) {
return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
}
if (u > endRadian) {
return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
}
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate
},
z: function (u: any, v: any) {
if (u < -Math.PI * 0.5) {
return Math.sin(u)
}
if (u > Math.PI * 2.5) {
return Math.sin(u) * h * 0.1
}
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1
}
}
}
//
watch(
() => props.chartConfig.option.dataset,
newData => {
try {
if (!isPreview()) {
dataHandle(newData)
} else {
// option.value = props.chartConfig.option
// dataHandle(newData)
dataHandle(newData)
}
} catch (error) {
console.log(error)
}
},
{
deep: false
}
)
watch(
() => props.chartConfig.option.grid3D.viewControl.alpha,
newData => {
props.chartConfig.option.grid3D.viewControl.alpha = newData
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.grid3D.viewControl.distance,
newData => {
props.chartConfig.option.grid3D.viewControl.distance = newData
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.grid3D.boxHeight,
newData => {
props.chartConfig.option.grid3D.boxHeight = newData
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.legend.orient,
newData => {
props.chartConfig.option.orient = newData
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.showLableLineImage,
newData => {
props.chartConfig.option.showLableLineImage = newData
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label) {
props.chartConfig.option.series[3].label.formatter = newData
? props.chartConfig.option.labelStyleType === '样式一'
? ' {c|{c}个}\n\n {img|}\n\n {a| 占比{d}%}'
: props.chartConfig.option.labelStyleType === '样式二'
? ' {style|{c} 个}\n\n {img|}\n\n {e|{b}}'
: ' {style|{d}%}\n\n {img|}\n\n {f|{b}}'
: props.chartConfig.option.labelStyleType === '样式一'
? ' {c|{c}个}\n\n {dot|}\n\n {a| 占比{d}%}'
: props.chartConfig.option.labelStyleType === '样式二'
? ' {style|{c} 个}\n\n {dot|}\n\n{e|{b}}'
: ' {style|{d}%}\n\n {dot|}\n\n {f|{b}}'
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.labelLineColor,
newData => {
props.chartConfig.option.labelLineColor = newData
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].labelLine) {
props.chartConfig.option.series[3].labelLine.lineStyle.color = newData
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.labelPadding1,
newData => {
props.chartConfig.option.labelPadding1 = newData
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label && newData) {
props.chartConfig.option.series[3].label.rich.c.padding = newData.split(',').map(function (str: any) {
return parseInt(str)
})
props.chartConfig.option.series[3].label.rich.style.padding = newData.split(',').map(function (str: any) {
return parseInt(str)
})
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.labelPadding2,
newData => {
props.chartConfig.option.labelPadding2 = newData
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label && newData) {
props.chartConfig.option.series[3].label.rich.a.padding = newData.split(',').map(function (str: any) {
return parseInt(str)
})
props.chartConfig.option.series[3].label.rich.e.padding = newData.split(',').map(function (str: any) {
return parseInt(str)
})
props.chartConfig.option.series[3].label.rich.f.padding = newData.split(',').map(function (str: any) {
return parseInt(str)
})
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.labelColor1,
newData => {
props.chartConfig.option.labelColor1 = newData
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label) {
props.chartConfig.option.series[3].label.rich = {
dot: {
backgroundColor: '#fff', //
width: 0,
height: 0,
borderRadius: 5, //
fontSize: 16,
padding: [5, -5, 5, -5] // 6
},
img: {
backgroundColor: { image: '/src/assets/images/chart/charts/dot.png' }, //
width: 24,
height: 24,
// borderRadius: 5, //
// fontSize: 16,
padding: [0, -30, 0, -15]
},
a: {
fontSize: 14,
color: props.chartConfig.option.labelColor2 || '#fff',
align: 'left',
padding: [0, 3, 0, -5]
},
c: {
fontSize: 20,
color: newData || '#41a6fc',
align: 'left',
padding: [0, 3, -10, 5]
},
style: {
fontSize: 20,
color: newData || '#b8c276',
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
// { offset: 0, color: '#fefefe' }, //
// { offset: 1, color: '#e0e98d' } //
// ]),
align: 'left',
padding: [0, 3, -10, 5]
},
e: {
fontSize: 14,
color: props.chartConfig.option.labelColor2 || '#fff',
align: 'left',
padding: [0, 0, 0, 23]
},
f: {
fontSize: 14,
color: props.chartConfig.option.labelColor2 || '#fff',
align: 'left',
padding: [0, 0, 0, 3]
}
}
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.labelColor2,
newData => {
props.chartConfig.option.labelColor2 = newData
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label) {
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.lableLineLength1,
newData => {
props.chartConfig.option.lableLineLength1 = newData
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].labelLine) {
props.chartConfig.option.series[3].labelLine.length = newData
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.lableLineLength2,
newData => {
props.chartConfig.option.lableLineLength2 = newData
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].labelLine) {
props.chartConfig.option.series[3].labelLine.length2 = newData
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.labelStyleType,
newData => {
props.chartConfig.option.labelStyleType = newData
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label) {
props.chartConfig.option.series[3].label.formatter = props.chartConfig.option.showLableLineImage
? newData === '样式一'
? ' {c|{c}个}\n\n {img|}\n\n {a| 占比{d}%}'
: newData === '样式二'
? ' {style|{c} 个}\n\n {img|}\n\n {e|{b}}'
: ' {style|{d}%}\n\n {img|}\n\n {f|{b}}'
: newData === '样式一'
? ' {c|{c}个}\n\n {dot|}\n\n {a| 占比{d}%}'
: newData === '样式二'
? ' {style|{c} 个}\n\n {dot|}\n\n{e|{b}}'
: ' {style|{d}%}\n\n {dot|}\n\n {f|{b}}'
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.showDataLabels,
newData => {
if (props.chartConfig.option.series.length > 1) {
let lastSeries = props.chartConfig.option.series[props.chartConfig.option.series.length - 1]
if (newData) {
lastSeries.label.opacity = 1
lastSeries.labelLine.show = true
} else {
lastSeries.label.opacity = 0
lastSeries.labelLine.show = false
}
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.showLegendValue,
newData => {
if (newData) {
props.chartConfig.option.legend.formatter = (name: any) => {
//
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
//
if (props.chartConfig.option.showLegendPercent) {
//
var total = 0
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
total += props.chartConfig.option.dataset[i].value
}
//
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
//
return name + ' ' + value + ' ' + ((value / total) * 100).toFixed(1) + '%'
}
return name + ' ' + value
// }
// return name;
}
} else {
props.chartConfig.option.legend.formatter = (name: any) => {
if (props.chartConfig.option.showLegendPercent) {
//
var total = 0
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
total += props.chartConfig.option.dataset[i].value
}
//
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
//
return name + ' ' + ((value / total) * 100).toFixed(1) + '%'
}
return name
}
}
},
{
immediate: true,
deep: false
}
)
watch(
() => props.chartConfig.option.showLegendPercent,
newData => {
if (props.chartConfig.option.showLegendValue) {
props.chartConfig.option.legend.formatter = (name: any) => {
//
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
//
if (newData) {
//
var total = 0
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
total += props.chartConfig.option.dataset[i].value
}
//
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
//
return name + ' ' + value + ' ' + ((value / total) * 100).toFixed(1) + '%'
}
return name + ' ' + value
}
} else {
props.chartConfig.option.legend.formatter = (name: any) => {
if (newData) {
//
var total = 0
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
total += props.chartConfig.option.dataset[i].value
}
//
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
//
return name + ' ' + ((value / total) * 100).toFixed(1) + '%'
}
return name
}
}
},
{
immediate: true,
deep: false
}
)
onMounted(() => {
dataHandle(props.chartConfig.option.dataset)
if (showLegendValue?.value) {
props.chartConfig.option.legend.formatter = (name: any) => {
//
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
//
if (showLegendPercent?.value) {
//
var total = 0
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
total += props.chartConfig.option.dataset[i].value
}
//
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
//
return name + ' ' + value + ' ' + ((value / total) * 100).toFixed(1) + '%'
}
return name + ' ' + value
// }
// return name;
}
} else {
props.chartConfig.option.legend.formatter = (name: any) => {
if (showLegendPercent?.value) {
//
var total = 0
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
total += props.chartConfig.option.dataset[i].value
}
//
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
//
return name + ' ' + ((value / total) * 100).toFixed(1) + '%'
}
return name
}
}
})
const handleHighlight = (params: any) => {
if (params.seriesName !== 'mouseoutSeries') {
let series = vChartRef.value?.getOption().series
series[params.seriesIndex].parametricEquation = getParametricEquation(
series[params.seriesIndex].pieData.startRatio,
series[params.seriesIndex].pieData.endRatio,
false,
true, //
series[params.seriesIndex].pieStatus.k,
series[params.seriesIndex].pieData.value * 1.05 //
)
vChartRef.value?.setOption({ series: series })
}
}
const cancelHighlight = (params: any) => {
let series = vChartRef.value?.getOption().series
series[params.seriesIndex].parametricEquation = getParametricEquation(
series[params.seriesIndex].pieData.startRatio,
series[params.seriesIndex].pieData.endRatio,
false,
false, //
series[params.seriesIndex].pieStatus.k,
series[params.seriesIndex].pieData.value //
)
vChartRef.value?.setOption({ series: series })
}
//
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
props.chartConfig.option.dataset = newData
dataHandle(newData)
})
</script>
<style scoped lang="scss"></style>

View File

@ -2,5 +2,6 @@ import { PieCommonConfig } from './PieCommon/index'
import { PieCircleConfig } from './PieCircle/index'
import { PieThreeConfig } from './PieThree'
import { PieMultConfig } from './PieMult/index'
import {PieConfig} from './Pie/index'
export default [PieCommonConfig, PieCircleConfig, PieThreeConfig, PieMultConfig]
export default [PieCommonConfig, PieCircleConfig, PieThreeConfig, PieMultConfig, PieConfig]